Contract 0x904aE1a25757E59456a59a49c4dE52D68DcCf9ff 3

 
 
Txn Hash
Method
Block
From
To
Value
0x16e8924380df5daf74b194bea15a4df807eb09876288297e515dec3c836bba68Set Approval For...165205272023-01-30 15:26:354 days 6 hrs ago0x3f457ead446a52db61801803338c3dcdc68c712f IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.00094051 20.41973461
0x80af19a6bfe2f109a5a45946163e65800d79ddd19924d28e39408aedda75d90aSet Approval For...165189502023-01-30 10:09:474 days 12 hrs ago0x5a1e8287ef7af8e84c0c30736f114236bb9596b2 IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.00076405 16.57995463
0xba3c06d098483abd4e5ffddd83ae1b9449f87dfaf24dcdf5268b7abdc42bc638Set Approval For...165168632023-01-30 3:10:234 days 19 hrs agoENS Name barbershop.eth IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.00036126 14.96105745
0x924345eb3b08e01553837b6bb665079a87476eea3d49550a1606d85754f011c4Set Approval For...165168612023-01-30 3:09:594 days 19 hrs agoENS Name barbershop.eth IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.00036871 15.25441802
0xb93713376fbfbe7c69ca46a9ae8cb4d1d1b8212a251d2c4e1f86ea5b65a48278Set Approval For...164944582023-01-27 0:04:357 days 22 hrs ago0x35821f395bcbc06fd70e7275c35a4d7f827ceab8 IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.00085757 18.61906424
0xd158fd4e7de536518933bbcca870c3af2f54220490c51dac01b558f2974f17d2Set Approval For...164940762023-01-26 22:48:117 days 23 hrs ago0x41f20e7b9d36bd0424e7c06bfe4c20c2d37a03b5 IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.0012358 26.83082971
0x1c6059728db98f7e4c8a4769ab889eceffa7d207641af4b014b4ed7ab3f98303Unlock And Trans...164861142023-01-25 20:06:599 days 2 hrs ago0x17a4e2169c0d05e79ed465558ff27824e8521007 IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.00518768 30.36327441
0x6e1b3a57d4f4eff77b71931949c62946cf2e64a1de3cd83be4aa02645c6dc260Unlock And Trans...164861122023-01-25 20:06:359 days 2 hrs ago0x17a4e2169c0d05e79ed465558ff27824e8521007 IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.00551425 34.03169822
0x017e3ff07cdf0d8313e54c994d40a53c43a978ab17728b09473061520ca69d87Unlock And Trans...164856842023-01-25 18:40:239 days 3 hrs ago0x250674bf12f30aa56661bcae19e07128d0f417a0 IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.00194497 23.5338583
0x35792911aaf2acfca38ef6e7e00f0247c52e83bb2d77d97297a95722102d0280Set Approval For...164807282023-01-25 2:03:239 days 20 hrs ago0xa8a8782b50f97a048068ed703dc50d108f85afdc IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.0007762 16.85247269
0x224afd4ad668fc0dc40297b7a09392547512c0b91209fa227c2d96fd8f3255acUnlock And Trans...164807172023-01-25 2:01:119 days 20 hrs ago0xa8a8782b50f97a048068ed703dc50d108f85afdc IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.00254778 18.79300324
0x260371dfedcdf8f3edbcc1bc3567e20b503f023084fa605366a873fc69d0045bSafe Transfer Fr...164802652023-01-25 0:30:479 days 21 hrs ago0x7f73903205db9cf15eff85ea6df9669741a2e378 IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.005492 19.22539899
0xa60fad54c1ac7b12b4197989cc372b9529cb82e0ff444088c574ff87c3ee5670Safe Transfer Fr...164799362023-01-24 23:24:599 days 22 hrs ago0xdf0635793e91d4f8e7426dbd9ed08471186f428d IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.00166909 27.87160211
0xf9ccca444f30d2b80e4e8c0210216d280abaf320c8dd581fb64aa11122081ef4Safe Transfer Fr...164769482023-01-24 13:24:3510 days 8 hrs agoENS Name the1crow.eth IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.00089824 14.98343624
0xe6fdd718a78518264a7ca869ec049b7a7a91c898a41bf6cc0e3132a19014e8aeSafe Transfer Fr...164686572023-01-23 9:38:1111 days 12 hrs ago0xd88ef1665b253f56c1e2b1a47ecbc3c9f60dd833 IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.00092652 14.30955836
0x9113935414b3f9be4090891015b053adfd0476bb1d8168b0599c00ce114d1c53Set Approval For...164637822023-01-22 17:17:1112 days 5 hrs ago0x330153c61f909a19ef7c93bc9438eb2111ea2bb8 IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.00075272 16.34263943
0x69af8090daff39376a77001b7c744a7dac43a2a5745bbbc74e9ad4fc9c2ac89bSet Approval For...164610932023-01-22 8:16:4712 days 14 hrs agoENS Name yauchun.eth IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.00071853 15.60037029
0x7f50d7daac5f8526da37f81a19ef7c1938e6c80b2c4d110ac81ddc038bafac0bLock And Transmi...164602872023-01-22 5:34:4712 days 16 hrs ago0x205ffda46164c3e6ae60af559c82f26f9470072e IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.00342836 15.22810738
0xe883564e72aadc0ec3d1550659fe83bcbade6078684aa85de934edebd921b126Lock And Transmi...164468202023-01-20 8:28:2314 days 13 hrs agoENS Name zabbli.eth IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.00227862 16.87881792
0xb449cda5cbba8188a38906eb228aecb9b9d9618af3fc4adc68ff3d9b8c05b435Set Approval For...164378182023-01-19 2:18:1115 days 20 hrs ago0x2388b07d9c438a8c15f459e732f6871cca35555d IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.00081001 17.58653858
0xebe19fb9e8bc4b1ae6d5d0068d508d82d078178fbd7b1a86f320539c3754a5aaSafe Transfer Fr...164247882023-01-17 6:40:5917 days 15 hrs ago0x3d14f71807428a5de522c416c09653729725a05e IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.00096778 16.14348409
0xef79fb85489db552a7a41e3103f576f27069bd6b84884061213eba368fcc1b23Lock And Transmi...164233272023-01-17 1:47:4717 days 20 hrs ago0x205ffda46164c3e6ae60af559c82f26f9470072e IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.00480325 19.39391745
0xdf86f1a5352ebbf8548e75a3d4792eaa915a924ce86d9ee45da872e43c6c6286Set Approval For...164144042023-01-15 19:51:4719 days 2 hrs ago0x1198a5528e850019f2cae9d3445e7a7290d60ffd IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.00057399 23.77077865
0xc370cba064ac6d5384b2b5caa25a07177307c08ed63165f670c70730f56e11ffSet Approval For...164136612023-01-15 17:22:2319 days 4 hrs ago0xdfed7ba9bceb30a7d2ccbb9aac76fe31f47e7ded IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.00084316 18.30619796
0x1caac24744cfcf0e38c8064b46b8b344984cac319742727fefa8c8a3bf6c16a7Set Approval For...164114342023-01-15 9:53:2319 days 12 hrs ago0xfefdb4160da62fe0a6748deb1d2fb6067b349101 IN  0x904ae1a25757e59456a59a49c4de52d68dccf9ff0 Ether0.00072331 15.70403899
[ Download CSV Export 
Latest 1 internal transaction
Parent Txn Hash Block From To Value
0x15069e9c43aabf28e066e690983879177d4035d4aa95dba16ce0a644e342b4d1156826232022-10-05 14:53:35121 days 7 hrs ago 0x904ae1a25757e59456a59a49c4de52d68dccf9ff0x74de159b3a9372b7e85fd00569a0929265b630ef233.727 Ether
[ Download CSV Export 
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
GMC

Compiler Version
v0.8.15+commit.e14f2714

Optimization Enabled:
Yes with 100000 runs

Other Settings:
default evmVersion
File 1 of 20 : GMCRoot.sol
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {OwnableUDS} from "UDS/auth/OwnableUDS.sol";
import {LibCrumbMap} from "../lib/LibCrumbMap.sol";
import {FxERC721MRoot} from "ERC721M/extensions/FxERC721MRoot.sol";
import {ERC20UDS as ERC20} from "UDS/tokens/ERC20UDS.sol";

import "solady/utils/ECDSA.sol";
import "solady/utils/LibString.sol";

error ExceedsLimit();
error TransferFailed();
error TimelockActive();
error IncorrectValue();
error MaxSupplyLocked();
error InvalidSignature();
error InvalidPriceUnits();
error WhitelistNotActive();
error PublicSaleNotActive();
error ContractCallNotAllowed();

/// @title Gangsta Mice City Root
/// @author phaze (https://github.com/0xPhaze)
contract GMC is OwnableUDS, FxERC721MRoot {
    using ECDSA for bytes32;
    using LibString for uint256;
    using LibCrumbMap for LibCrumbMap.CrumbMap;

    event SaleStateUpdate();
    event FirstLegendaryRaffleEntered(address user);
    event SecondLegendaryRaffleEntered(address user);

    uint16 public constant MAX_PER_WALLET = 20;
    uint256 public constant PURCHASE_LIMIT = 5;
    uint256 public constant BRIDGE_RAFFLE_LOCK_DURATION = 24 hours;
    uint256 private constant PRICE_UNIT = 0.001 ether;
    uint256 private constant GENESIS_CLAIM = 555;
    uint256 private immutable DEPLOY_TIMESTAMP;

    bool public maxSupplyLocked;
    uint16 public supply;
    uint16 public maxSupply;
    uint32 public mintStart;
    uint8 private publicPriceUnits;
    uint8 private whitelistPriceUnits;
    address private signer;

    string private baseURI;
    string private postFixURI = ".json";
    string private unrevealedURI = "ipfs://QmTv9VoXgkZxFcomTW3kN6CRryUPMfgeUkVekFszcd79gK/";

    LibCrumbMap.CrumbMap gangs;

    constructor(address checkpointManager, address fxRoot)
        FxERC721MRoot("Gangsta Mice City", "GMC", checkpointManager, fxRoot)
    {
        __Ownable_init();

        maxSupply = 6666;
        signer = msg.sender;
        DEPLOY_TIMESTAMP = block.timestamp;

        publicPriceUnits = toPriceUnits(0.049 ether);
        whitelistPriceUnits = toPriceUnits(0.039 ether);
    }

    /* ------------- view ------------- */

    function totalSupply() public view override returns (uint256) {
        return supply;
    }

    function publicPrice() public view returns (uint256) {
        return toPrice(publicPriceUnits);
    }

    function whitelistPrice() public view returns (uint256) {
        return toPrice(whitelistPriceUnits);
    }

    function gangOf(uint256 id) public view returns (uint256) {
        return gangs.get(id);
    }

    /* ------------- external ------------- */

    function mint(uint256 quantity, bool lock)
        external
        payable
        onlyEOA
        requireMintableSupply(quantity)
        requireMintableByUser(quantity, MAX_PER_WALLET)
    {
        unchecked {
            if (msg.value != publicPrice() * quantity) revert IncorrectValue();
            if (block.timestamp < mintStart + 2 hours || mintStart == 0) revert PublicSaleNotActive();

            mintWithPerks(msg.sender, quantity, lock);
        }
    }

    function whitelistMint(
        uint256 quantity,
        bool lock,
        uint256 limit,
        bytes calldata signature
    ) external payable onlyEOA requireMintableSupply(quantity) requireMintableByUser(quantity, limit) {
        unchecked {
            if (!validSignature(signature, limit)) revert InvalidSignature();
            if (mintStart + 2 hours < block.timestamp) revert WhitelistNotActive();
            if (msg.value != whitelistPrice() * quantity) revert IncorrectValue();

            mintWithPerks(msg.sender, quantity, lock);
        }
    }

    function lockAndTransmit(address from, uint256[] calldata tokenIds) external {
        unchecked {
            if (tokenIds.length > 20) revert ExceedsLimit();
            // don't repeat an unnecessary sload if we can avoid it
            if (
                tokenIds.length != 0 &&
                block.timestamp < DEPLOY_TIMESTAMP + 1 weeks &&
                block.timestamp < mintStart + 2 hours
            ) {
                emit SecondLegendaryRaffleEntered(from);
            }

            _lockAndTransmit(from, tokenIds);
        }
    }

    function unlockAndTransmit(address from, uint256[] calldata tokenIds) external {
        if (tokenIds.length > 20) revert ExceedsLimit();
        if (block.timestamp < DEPLOY_TIMESTAMP + 1 weeks && block.timestamp < mintStart + BRIDGE_RAFFLE_LOCK_DURATION) {
            revert TimelockActive();
        }

        _unlockAndTransmit(from, tokenIds);
    }

    /* ------------- private ------------- */

    function validSignature(bytes calldata signature, uint256 limit) private view returns (bool) {
        bytes32 hash = keccak256(abi.encode(address(this), msg.sender, limit));
        address recovered = hash.toEthSignedMessageHash().recover(signature);

        return recovered != address(0) && recovered == signer;
    }

    function toPrice(uint16 priceUnits) private pure returns (uint256) {
        unchecked {
            return uint256(priceUnits) * PRICE_UNIT;
        }
    }

    function toPriceUnits(uint256 price) private pure returns (uint8) {
        unchecked {
            uint256 units;

            if (price % PRICE_UNIT != 0) revert InvalidPriceUnits();
            if ((units = price / PRICE_UNIT) > type(uint8).max) revert InvalidPriceUnits();

            return uint8(units);
        }
    }

    function mintWithPerks(
        address to,
        uint256 quantity,
        bool lock
    ) private {
        unchecked {
            if (quantity > 2) {
                emit FirstLegendaryRaffleEntered(to);

                if (supply < 500 + GENESIS_CLAIM) ++quantity;
            }

            if (lock && block.timestamp < mintStart + 2 hours) emit SecondLegendaryRaffleEntered(to);

            if (lock) _mintLockedAndTransmit(to, quantity);
            else _mint(to, quantity);
        }
    }

    /* ------------- owner ------------- */

    function pause() external onlyOwner {
        mintStart = 0;
    }

    function lockMaxSupply() external onlyOwner {
        maxSupplyLocked = true;
    }

    function setSigner(address addr) external onlyOwner {
        signer = addr;
    }

    function setMaxSupply(uint16 value) external onlyOwner {
        if (maxSupplyLocked) revert MaxSupplyLocked();

        maxSupply = value;
    }

    function setMintStart(uint32 time) external onlyOwner {
        mintStart = time;
    }

    function setPublicPrice(uint256 value) external onlyOwner {
        publicPriceUnits = toPriceUnits(value);
    }

    function setBaseURI(string calldata uri) external onlyOwner {
        baseURI = uri;
    }

    function setPostFixURI(string calldata postFix) external onlyOwner {
        postFixURI = postFix;
    }

    function setWhitelistPrice(uint256 value) external onlyOwner {
        whitelistPriceUnits = toPriceUnits(value);
    }

    function setUnrevealedURI(string calldata uri) external onlyOwner {
        unrevealedURI = uri;
    }

    function setGangs(uint256[] calldata chunkIndices, uint256[] calldata chunks) external onlyOwner {
        for (uint256 i; i < chunkIndices.length; ++i) gangs.set32BytesChunk(chunkIndices[i], chunks[i]);
    }

    function airdrop(
        address[] calldata users,
        uint256 quantity,
        bool locked
    ) external onlyOwner requireMintableSupply(quantity * users.length) {
        if (locked) for (uint256 i; i < users.length; ++i) _mintLockedAndTransmit(users[i], quantity);
        else for (uint256 i; i < users.length; ++i) _mint(users[i], quantity);
    }

    function withdraw() external onlyOwner {
        uint256 balance = address(this).balance;
        (bool success, ) = msg.sender.call{value: balance}("");

        if (!success) revert TransferFailed();
    }

    function recoverToken(ERC20 token) external onlyOwner {
        uint256 balance = token.balanceOf(address(this));

        token.transfer(msg.sender, balance);
    }

    /* ------------- override ------------- */

    function _authorizeTunnelController() internal override onlyOwner {}

    function _increaseTotalSupply(uint256 amount) internal override {
        supply += uint16(amount);
    }

    /* ------------- modifier ------------- */

    modifier onlyEOA() {
        if (tx.origin != msg.sender) revert ContractCallNotAllowed();
        _;
    }

    modifier requireMintableByUser(uint256 quantity, uint256 limit) {
        unchecked {
            if (quantity > PURCHASE_LIMIT) revert ExceedsLimit();
            if (quantity + numMinted(msg.sender) > limit) revert ExceedsLimit();
        }
        _;
    }

    modifier requireMintableSupply(uint256 quantity) {
        unchecked {
            if (quantity + supply > maxSupply) revert ExceedsLimit();
        }
        _;
    }

    /* ------------- ERC721 ------------- */

    function tokenURI(uint256 id) public view override returns (string memory) {
        return 
            bytes(baseURI).length == 0 
              ? unrevealedURI 
              : string.concat(baseURI, id.toString(), postFixURI); // prettier-ignore
    }
}

File 2 of 20 : OwnableUDS.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Context} from "../utils/Context.sol";
import {Initializable} from "../utils/Initializable.sol";

// ------------- storage

bytes32 constant DIAMOND_STORAGE_OWNABLE = keccak256("diamond.storage.ownable");

function s() pure returns (OwnableDS storage diamondStorage) {
    bytes32 slot = DIAMOND_STORAGE_OWNABLE;
    assembly { diamondStorage.slot := slot } // prettier-ignore
}

struct OwnableDS {
    address owner;
}

// ------------- errors

error CallerNotOwner();

/// @title Ownable (Upgradeable Diamond Storage)
/// @author phaze (https://github.com/0xPhaze/UDS)
/// @dev Requires `__Ownable_init` to be called in proxy
abstract contract OwnableUDS is Context, Initializable {
    OwnableDS private __storageLayout; // storage layout for upgrade compatibility checks

    event OwnerChanged(address oldOwner, address newOwner);

    function __Ownable_init() internal initializer {
        s().owner = _msgSender();
    }

    /* ------------- external ------------- */

    function owner() public view returns (address) {
        return s().owner;
    }

    function transferOwnership(address newOwner) external onlyOwner {
        s().owner = newOwner;

        emit OwnerChanged(_msgSender(), newOwner);
    }

    /* ------------- modifier ------------- */

    modifier onlyOwner() {
        if (_msgSender() != s().owner) revert CallerNotOwner();
        _;
    }
}

File 3 of 20 : LibCrumbMap.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

using LibCrumbMap for LibCrumbMap.CrumbMap;


/// @notice Efficient crumb map library for mapping integers to crumbs.
/// @author phaze (https://github.com/0xPhaze)
/// @author adapted from Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibBytemap.sol)
library LibCrumbMap {
    struct CrumbMap {
        mapping(uint256 => uint256) map;
    }

    /* ------------- CrumbMap ------------- */

    function get(CrumbMap storage crumbMap, uint256 index) internal view returns (uint256 result) {
        assembly {
            mstore(0x20, crumbMap.slot)
            mstore(0x00, shr(7, index))
            result := and(shr(shl(1, and(index, 0x7f)), sload(keccak256(0x00, 0x20))), 0x03)
        }
    }

    function get32BytesChunk(CrumbMap storage crumbMap, uint256 bytesIndex) internal view returns (uint256 result) {
        assembly {
            mstore(0x20, crumbMap.slot)
            mstore(0x00, bytesIndex)
            result := sload(keccak256(0x00, 0x20))
        }
    }

    function set32BytesChunk(
        CrumbMap storage crumbMap,
        uint256 bytesIndex,
        uint256 value
    ) internal {
        assembly {
            mstore(0x20, crumbMap.slot)
            mstore(0x00, bytesIndex)
            sstore(keccak256(0x00, 0x20), value)
        }
    }

    function set(
        CrumbMap storage crumbMap,
        uint256 index,
        uint256 value
    ) internal {
        require(value < 4);

        assembly {
            mstore(0x20, crumbMap.slot)
            mstore(0x00, shr(7, index))
            let storageSlot := keccak256(0x00, 0x20)
            let shift := shl(1, and(index, 0x7f))
            // Unset crumb at index and store.
            let chunkValue := and(sload(storageSlot), not(shl(shift, 0x03)))
            // Set crumb to `value` at index and store.
            chunkValue := or(chunkValue, shl(shift, value))
            sstore(storageSlot, chunkValue)
        }
    }

    /* ------------- mapping(uint256 => uint256) ------------- */

    function get(mapping(uint256 => uint256) storage crumbMap, uint256 index) internal view returns (uint256 result) {
        assembly {
            mstore(0x20, crumbMap.slot)
            mstore(0x00, shr(7, index))
            result := and(shr(shl(1, and(index, 0x7f)), sload(keccak256(0x00, 0x20))), 0x03)
        }
    }

    function get32BytesChunk(mapping(uint256 => uint256) storage crumbMap, uint256 bytesIndex) internal view returns (uint256 result) {
        assembly {
            mstore(0x20, crumbMap.slot)
            mstore(0x00, bytesIndex)
            result := sload(keccak256(0x00, 0x20))
        }
    }

    function set32BytesChunk(
        mapping(uint256 => uint256) storage crumbMap,
        uint256 bytesIndex,
        uint256 value
    ) internal {
        assembly {
            mstore(0x20, crumbMap.slot)
            mstore(0x00, bytesIndex)
            sstore(keccak256(0x00, 0x20), value)
        }
    }

    function set(
        mapping(uint256 => uint256) storage crumbMap,
        uint256 index,
        uint256 value
    ) internal {
        require(value < 4);

        assembly {
            mstore(0x20, crumbMap.slot)
            mstore(0x00, shr(7, index))
            let storageSlot := keccak256(0x00, 0x20)
            let shift := shl(1, and(index, 0x7f))
            // Unset crumb at index and store.
            let chunkValue := and(sload(storageSlot), not(shl(shift, 0x03)))
            // Set crumb to `value` at index and store.
            chunkValue := or(chunkValue, shl(shift, value))
            sstore(storageSlot, chunkValue)
        }
    }
}

File 4 of 20 : FxERC721MRoot.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ERC721M} from "../ERC721M.sol";
import {ERC721MQuery} from "./ERC721MQuery.sol";
import {FxERC721Root} from "fx-contracts/FxERC721Root.sol";

/// @title ERC721M FxPortal extension
/// @author phaze (https://github.com/0xPhaze/ERC721M)
abstract contract FxERC721MRoot is FxERC721Root, ERC721M, ERC721MQuery {
    constructor(
        string memory name,
        string memory symbol,
        address checkpointManager,
        address fxRoot
    ) ERC721M(name, symbol) FxERC721Root(checkpointManager, fxRoot) {}

    /* ------------- virtual ------------- */

    function tokenURI(uint256 id) external view virtual override returns (string memory);

    function _authorizeTunnelController() internal virtual override;

    /* ------------- internal ------------- */

    function _mintLockedAndTransmit(address to, uint256 quantity) internal virtual {
        _mintLockedAndTransmit(to, quantity, 0);
    }

    function _mintLockedAndTransmit(
        address to,
        uint256 quantity,
        uint48 auxData
    ) internal virtual {
        uint256 startId = _nextTokenId();

        _mintAndLock(to, quantity, true, auxData);

        uint256[] memory ids = new uint256[](quantity);

        unchecked {
            for (uint256 i; i < quantity; ++i) {
                ids[i] = startId + i;
            }
        }

        _registerERC721IdsWithChildMem(to, ids);
    }

    function _lockAndTransmit(address from, uint256[] calldata ids) internal virtual {
        unchecked {
            for (uint256 i; i < ids.length; ++i) {
                _lock(from, ids[i]);
            }
        }

        _registerERC721IdsWithChild(from, ids);
    }

    // @notice using `_unlockAndTransmit` is simple and easy
    // this assumes L1 state as the single source of truth
    // messages are always pushed L1 -> L2 without knowing state on L2
    // this means that NFTs should not be allowed to be traded/sold on L2
    // alternatively `_unlockWithProof` should be implemented requiring
    // a MPT inclusion proof.
    function _unlockAndTransmit(address from, uint256[] calldata ids) internal virtual {
        unchecked {
            for (uint256 i; i < ids.length; ++i) _unlock(from, ids[i]);
        }

        _registerERC721IdsWithChild(address(0), ids);
    }
}

File 5 of 20 : ERC20UDS.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Context} from "../utils/Context.sol";
import {Initializable} from "../utils/Initializable.sol";
import {EIP712PermitUDS} from "../auth/EIP712PermitUDS.sol";

// ------------- storage

bytes32 constant DIAMOND_STORAGE_ERC20 = keccak256("diamond.storage.erc20");

function s() pure returns (ERC20DS storage diamondStorage) {
    bytes32 slot = DIAMOND_STORAGE_ERC20;
    assembly { diamondStorage.slot := slot } // prettier-ignore
}

struct ERC20DS {
    string name;
    string symbol;
    uint8 decimals;
    uint256 totalSupply;
    mapping(address => uint256) balanceOf;
    mapping(address => mapping(address => uint256)) allowance;
}

/// @title ERC20 (Upgradeable Diamond Storage)
/// @author phaze (https://github.com/0xPhaze/UDS)
/// @author Modified from Solmate (https://github.com/Rari-Capital/solmate)
abstract contract ERC20UDS is Context, Initializable, EIP712PermitUDS {
    ERC20DS private __storageLayout; // storage layout for upgrade compatibility checks

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

    /* ------------- init ------------- */

    function __ERC20_init(
        string memory _name,
        string memory _symbol,
        uint8 _decimals
    ) internal initializer {
        s().name = _name;
        s().symbol = _symbol;
        s().decimals = _decimals;
    }

    /* ------------- view ------------- */

    function name() external view virtual returns (string memory) {
        return s().name;
    }

    function symbol() external view virtual returns (string memory) {
        return s().symbol;
    }

    function decimals() external view virtual returns (uint8) {
        return s().decimals;
    }

    function totalSupply() external view virtual returns (uint256) {
        return s().totalSupply;
    }

    function balanceOf(address owner) public view virtual returns (uint256) {
        return s().balanceOf[owner];
    }

    function allowance(address owner, address operator) public view virtual returns (uint256) {
        return s().allowance[owner][operator];
    }

    /* ------------- public ------------- */

    function approve(address operator, uint256 amount) public virtual returns (bool) {
        s().allowance[_msgSender()][operator] = amount;

        emit Approval(_msgSender(), operator, amount);

        return true;
    }

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

        unchecked {
            s().balanceOf[to] += amount;
        }

        emit Transfer(_msgSender(), to, amount);

        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual returns (bool) {
        uint256 allowed = s().allowance[from][_msgSender()];

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

        s().balanceOf[from] -= amount;

        unchecked {
            s().balanceOf[to] += amount;
        }

        emit Transfer(from, to, amount);

        return true;
    }

    // EIP-2612 permit
    function permit(
        address owner,
        address operator,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s_
    ) public virtual {
        _usePermit(owner, operator, value, deadline, v, r, s_);

        s().allowance[owner][operator] = value;

        emit Approval(owner, operator, value);
    }

    /* ------------- internal ------------- */

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

        unchecked {
            s().balanceOf[to] += amount;
        }

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

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

        unchecked {
            s().totalSupply -= amount;
        }

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

File 6 of 20 : ECDSA.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Gas optimized ECDSA wrapper.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/ECDSA.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ECDSA.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol)
library ECDSA {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The number which `s` must not exceed in order for
    /// the signature to be non-malleable.
    bytes32 private constant _MALLEABILITY_THRESHOLD =
        0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                    RECOVERY OPERATIONS                     */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Recovers the signer's address from a message digest `hash`,
    /// and the `signature`.
    ///
    /// This function does NOT accept EIP-2098 short form signatures.
    /// Use `recover(bytes32 hash, bytes32 r, bytes32 vs)` for EIP-2098
    /// short form signatures instead.
    ///
    /// WARNING!
    /// The `result` will be the zero address upon recovery failure.
    /// As such, it is extremely important to ensure that the address which
    /// the `result` is compared against is never zero.
    function recover(bytes32 hash, bytes calldata signature) internal view returns (address result) {
        assembly {
            if eq(signature.length, 65) {
                // Copy the free memory pointer so that we can restore it later.
                let m := mload(0x40)
                // Directly copy `r` and `s` from the calldata.
                calldatacopy(0x40, signature.offset, 0x40)

                // If `s` in lower half order, such that the signature is not malleable.
                if iszero(gt(mload(0x60), _MALLEABILITY_THRESHOLD)) {
                    mstore(0x00, hash)
                    // Compute `v` and store it in the scratch space.
                    mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40))))
                    pop(
                        staticcall(
                            gas(), // Amount of gas left for the transaction.
                            0x01, // Address of `ecrecover`.
                            0x00, // Start of input.
                            0x80, // Size of input.
                            0x40, // Start of output.
                            0x20 // Size of output.
                        )
                    )
                    // Restore the zero slot.
                    mstore(0x60, 0)
                    // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
                    result := mload(sub(0x60, returndatasize()))
                }
                // Restore the free memory pointer.
                mstore(0x40, m)
            }
        }
    }

    /// @dev Recovers the signer's address from a message digest `hash`,
    /// and the EIP-2098 short form signature defined by `r` and `vs`.
    ///
    /// This function only accepts EIP-2098 short form signatures.
    /// See: https://eips.ethereum.org/EIPS/eip-2098
    ///
    /// To be honest, I do not recommend using EIP-2098 signatures
    /// for simplicity, performance, and security reasons. Most if not
    /// all clients support traditional non EIP-2098 signatures by default.
    /// As such, this method is intentionally not fully inlined.
    /// It is merely included for completeness.
    ///
    /// WARNING!
    /// The `result` will be the zero address upon recovery failure.
    /// As such, it is extremely important to ensure that the address which
    /// the `result` is compared against is never zero.
    function recover(
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal view returns (address result) {
        uint8 v;
        bytes32 s;
        assembly {
            s := shr(1, shl(1, vs))
            v := add(shr(255, vs), 27)
        }
        result = recover(hash, v, r, s);
    }

    /// @dev Recovers the signer's address from a message digest `hash`,
    /// and the signature defined by `v`, `r`, `s`.
    ///
    /// WARNING!
    /// The `result` will be the zero address upon recovery failure.
    /// As such, it is extremely important to ensure that the address which
    /// the `result` is compared against is never zero.
    function recover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal view returns (address result) {
        assembly {
            // Copy the free memory pointer so that we can restore it later.
            let m := mload(0x40)

            // If `s` in lower half order, such that the signature is not malleable.
            if iszero(gt(s, _MALLEABILITY_THRESHOLD)) {
                mstore(0x00, hash)
                mstore(0x20, v)
                mstore(0x40, r)
                mstore(0x60, s)
                pop(
                    staticcall(
                        gas(), // Amount of gas left for the transaction.
                        0x01, // Address of `ecrecover`.
                        0x00, // Start of input.
                        0x80, // Size of input.
                        0x40, // Start of output.
                        0x20 // Size of output.
                    )
                )
                // Restore the zero slot.
                mstore(0x60, 0)
                // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
                result := mload(sub(0x60, returndatasize()))
            }
            // Restore the free memory pointer.
            mstore(0x40, m)
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                     HASHING OPERATIONS                     */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns an Ethereum Signed Message, created from a `hash`.
    /// This produces a hash corresponding to the one signed with the
    /// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign)
    /// JSON-RPC method as part of EIP-191.
    function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) {
        assembly {
            // Store into scratch space for keccak256.
            mstore(0x20, hash)
            mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32")
            // 0x40 - 0x04 = 0x3c
            result := keccak256(0x04, 0x3c)
        }
    }

    /// @dev Returns an Ethereum Signed Message, created from `s`.
    /// This produces a hash corresponding to the one signed with the
    /// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign)
    /// JSON-RPC method as part of EIP-191.
    function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) {
        assembly {
            // We need at most 128 bytes for Ethereum signed message header.
            // The max length of the ASCII reprenstation of a uint256 is 78 bytes.
            // The length of "\x19Ethereum Signed Message:\n" is 26 bytes (i.e. 0x1a).
            // The next multiple of 32 above 78 + 26 is 128 (i.e. 0x80).

            // Instead of allocating, we temporarily copy the 128 bytes before the
            // start of `s` data to some variables.
            let m3 := mload(sub(s, 0x60))
            let m2 := mload(sub(s, 0x40))
            let m1 := mload(sub(s, 0x20))
            // The length of `s` is in bytes.
            let sLength := mload(s)

            let ptr := add(s, 0x20)

            // `end` marks the end of the memory which we will compute the keccak256 of.
            let end := add(ptr, sLength)

            // Convert the length of the bytes to ASCII decimal representation
            // and store it into the memory.
            // prettier-ignore
            for { let temp := sLength } 1 {} {
                ptr := sub(ptr, 1)
                mstore8(ptr, add(48, mod(temp, 10)))
                temp := div(temp, 10)
                // prettier-ignore
                if iszero(temp) { break }
            }

            // Copy the header over to the memory.
            mstore(sub(ptr, 0x20), "\x00\x00\x00\x00\x00\x00\x19Ethereum Signed Message:\n")
            // Compute the keccak256 of the memory.
            result := keccak256(sub(ptr, 0x1a), sub(end, sub(ptr, 0x1a)))

            // Restore the previous memory.
            mstore(s, sLength)
            mstore(sub(s, 0x20), m1)
            mstore(sub(s, 0x40), m2)
            mstore(sub(s, 0x60), m3)
        }
    }
}

File 7 of 20 : LibString.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Library for converting numbers into strings and other string operations.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibString.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/LibString.sol)
library LibString {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                        CUSTOM ERRORS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The `length` of the output is too small to contain all the hex digits.
    error HexLengthInsufficient();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The constant returned when the `search` is not found in the string.
    uint256 internal constant NOT_FOUND = uint256(int256(-1));

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                     DECIMAL OPERATIONS                     */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns the base 10 decimal representation of `value`.
    function toString(uint256 value) internal pure returns (string memory str) {
        assembly {
            // The maximum value of a uint256 contains 78 digits (1 byte per digit), but
            // we allocate 0xa0 bytes to keep the free memory pointer 32-byte word aligned.
            // We will need 1 word for the trailing zeros padding, 1 word for the length,
            // and 3 words for a maximum of 78 digits. Total: 5 * 0x20 = 0xa0.
            let m := add(mload(0x40), 0xa0)
            // Update the free memory pointer to allocate.
            mstore(0x40, m)
            // Assign the `str` to the end.
            str := sub(m, 0x20)
            // Zeroize the slot after the string.
            mstore(str, 0)

            // Cache the end of the memory to calculate the length later.
            let end := str

            // We write the string from rightmost digit to leftmost digit.
            // The following is essentially a do-while loop that also handles the zero case.
            // prettier-ignore
            for { let temp := value } 1 {} {
                str := sub(str, 1)
                // Write the character to the pointer.
                // The ASCII index of the '0' character is 48.
                mstore8(str, add(48, mod(temp, 10)))
                // Keep dividing `temp` until zero.
                temp := div(temp, 10)
                // prettier-ignore
                if iszero(temp) { break }
            }

            let length := sub(end, str)
            // Move the pointer 32 bytes leftwards to make room for the length.
            str := sub(str, 0x20)
            // Store the length.
            mstore(str, length)
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                   HEXADECIMAL OPERATIONS                   */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns the hexadecimal representation of `value`,
    /// left-padded to an input length of `length` bytes.
    /// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte,
    /// giving a total length of `length * 2 + 2` bytes.
    /// Reverts if `length` is too small for the output to contain all the digits.
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory str) {
        assembly {
            let start := mload(0x40)
            // We need 0x20 bytes for the trailing zeros padding, `length * 2` bytes
            // for the digits, 0x02 bytes for the prefix, and 0x20 bytes for the length.
            // We add 0x20 to the total and round down to a multiple of 0x20.
            // (0x20 + 0x20 + 0x02 + 0x20) = 0x62.
            let m := add(start, and(add(shl(1, length), 0x62), not(0x1f)))
            // Allocate the memory.
            mstore(0x40, m)
            // Assign the `str` to the end.
            str := sub(m, 0x20)
            // Zeroize the slot after the string.
            mstore(str, 0)

            // Cache the end to calculate the length later.
            let end := str
            // Store "0123456789abcdef" in scratch space.
            mstore(0x0f, 0x30313233343536373839616263646566)

            let temp := value
            // We write the string from rightmost digit to leftmost digit.
            // The following is essentially a do-while loop that also handles the zero case.
            // prettier-ignore
            for {} 1 {} {
                str := sub(str, 2)
                mstore8(add(str, 1), mload(and(temp, 15)))
                mstore8(str, mload(and(shr(4, temp), 15)))
                temp := shr(8, temp)
                length := sub(length, 1)
                // prettier-ignore
                if iszero(length) { break }
            }

            if temp {
                // Store the function selector of `HexLengthInsufficient()`.
                mstore(0x00, 0x2194895a)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            // Compute the string's length.
            let strLength := add(sub(end, str), 2)
            // Move the pointer and write the "0x" prefix.
            str := sub(str, 0x20)
            mstore(str, 0x3078)
            // Move the pointer and write the length.
            str := sub(str, 2)
            mstore(str, strLength)
        }
    }

    /// @dev Returns the hexadecimal representation of `value`.
    /// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte.
    /// As address are 20 bytes long, the output will left-padded to have
    /// a length of `20 * 2 + 2` bytes.
    function toHexString(uint256 value) internal pure returns (string memory str) {
        assembly {
            let start := mload(0x40)
            // We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length,
            // 0x02 bytes for the prefix, and 0x40 bytes for the digits.
            // The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x40) is 0xa0.
            let m := add(start, 0xa0)
            // Allocate the memory.
            mstore(0x40, m)
            // Assign the `str` to the end.
            str := sub(m, 0x20)
            // Zeroize the slot after the string.
            mstore(str, 0)

            // Cache the end to calculate the length later.
            let end := str
            // Store "0123456789abcdef" in scratch space.
            mstore(0x0f, 0x30313233343536373839616263646566)

            // We write the string from rightmost digit to leftmost digit.
            // The following is essentially a do-while loop that also handles the zero case.
            // prettier-ignore
            for { let temp := value } 1 {} {
                str := sub(str, 2)
                mstore8(add(str, 1), mload(and(temp, 15)))
                mstore8(str, mload(and(shr(4, temp), 15)))
                temp := shr(8, temp)
                // prettier-ignore
                if iszero(temp) { break }
            }

            // Compute the string's length.
            let strLength := add(sub(end, str), 2)
            // Move the pointer and write the "0x" prefix.
            str := sub(str, 0x20)
            mstore(str, 0x3078)
            // Move the pointer and write the length.
            str := sub(str, 2)
            mstore(str, strLength)
        }
    }

    /// @dev Returns the hexadecimal representation of `value`.
    /// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte.
    function toHexString(address value) internal pure returns (string memory str) {
        assembly {
            let start := mload(0x40)
            // We need 0x20 bytes for the length, 0x02 bytes for the prefix,
            // and 0x28 bytes for the digits.
            // The next multiple of 0x20 above (0x20 + 0x02 + 0x28) is 0x60.
            str := add(start, 0x60)

            // Allocate the memory.
            mstore(0x40, str)
            // Store "0123456789abcdef" in scratch space.
            mstore(0x0f, 0x30313233343536373839616263646566)

            let length := 20
            // We write the string from rightmost digit to leftmost digit.
            // The following is essentially a do-while loop that also handles the zero case.
            // prettier-ignore
            for { let temp := value } 1 {} {
                str := sub(str, 2)
                mstore8(add(str, 1), mload(and(temp, 15)))
                mstore8(str, mload(and(shr(4, temp), 15)))
                temp := shr(8, temp)
                length := sub(length, 1)
                // prettier-ignore
                if iszero(length) { break }
            }

            // Move the pointer and write the "0x" prefix.
            str := sub(str, 32)
            mstore(str, 0x3078)
            // Move the pointer and write the length.
            str := sub(str, 2)
            mstore(str, 42)
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                   OTHER STRING OPERATIONS                  */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    // For performance and bytecode compactness, all indices of the following operations
    // are byte (ASCII) offsets, not UTF character offsets.

    /// @dev Returns `subject` all occurances of `search` replaced with `replacement`.
    function replace(
        string memory subject,
        string memory search,
        string memory replacement
    ) internal pure returns (string memory result) {
        assembly {
            let subjectLength := mload(subject)
            let searchLength := mload(search)
            let replacementLength := mload(replacement)

            subject := add(subject, 0x20)
            search := add(search, 0x20)
            replacement := add(replacement, 0x20)
            result := add(mload(0x40), 0x20)

            let subjectEnd := add(subject, subjectLength)
            if iszero(gt(searchLength, subjectLength)) {
                let subjectSearchEnd := add(sub(subjectEnd, searchLength), 1)
                let h := 0
                if iszero(lt(searchLength, 32)) {
                    h := keccak256(search, searchLength)
                }
                let m := shl(3, sub(32, and(searchLength, 31)))
                let s := mload(search)
                // prettier-ignore
                for {} 1 {} {
                    let t := mload(subject)
                    // Whether the first `searchLength % 32` bytes of 
                    // `subject` and `search` matches.
                    if iszero(shr(m, xor(t, s))) {
                        if h {
                            if iszero(eq(keccak256(subject, searchLength), h)) {
                                mstore(result, t)
                                result := add(result, 1)
                                subject := add(subject, 1)
                                // prettier-ignore
                                if iszero(lt(subject, subjectSearchEnd)) { break }
                                continue
                            }
                        }
                        // Copy the `replacement` one word at a time.
                        // prettier-ignore
                        for { let o := 0 } 1 {} {
                            mstore(add(result, o), mload(add(replacement, o)))
                            o := add(o, 0x20)
                            // prettier-ignore
                            if iszero(lt(o, replacementLength)) { break }
                        }
                        result := add(result, replacementLength)
                        subject := add(subject, searchLength)
                        if searchLength {
                            // prettier-ignore
                            if iszero(lt(subject, subjectSearchEnd)) { break }
                            continue
                        }
                    }
                    mstore(result, t)
                    result := add(result, 1)
                    subject := add(subject, 1)
                    // prettier-ignore
                    if iszero(lt(subject, subjectSearchEnd)) { break }
                }
            }

            let resultRemainder := result
            result := add(mload(0x40), 0x20)
            let k := add(sub(resultRemainder, result), sub(subjectEnd, subject))
            // Copy the rest of the string one word at a time.
            // prettier-ignore
            for {} lt(subject, subjectEnd) {} {
                mstore(resultRemainder, mload(subject))
                resultRemainder := add(resultRemainder, 0x20)
                subject := add(subject, 0x20)
            }
            result := sub(result, 0x20)
            // Zeroize the slot after the string.
            let last := add(add(result, 0x20), k)
            mstore(last, 0)
            // Allocate memory for the length and the bytes,
            // rounded up to a multiple of 32.
            mstore(0x40, and(add(last, 31), not(31)))
            // Store the length of the result.
            mstore(result, k)
        }
    }

    /// @dev Returns the byte index of the first location of `search` in `subject`,
    /// searching from left to right, starting from `from`.
    /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found.
    function indexOf(
        string memory subject,
        string memory search,
        uint256 from
    ) internal pure returns (uint256 result) {
        assembly {
            // prettier-ignore
            for { let subjectLength := mload(subject) } 1 {} {
                if iszero(mload(search)) {
                    // `result = min(from, subjectLength)`.
                    result := xor(from, mul(xor(from, subjectLength), lt(subjectLength, from)))
                    break
                }
                let searchLength := mload(search)
                let subjectStart := add(subject, 0x20)    
                
                result := not(0) // Initialize to `NOT_FOUND`.

                subject := add(subjectStart, from)
                let subjectSearchEnd := add(sub(add(subjectStart, subjectLength), searchLength), 1)

                let m := shl(3, sub(32, and(searchLength, 31)))
                let s := mload(add(search, 0x20))

                // prettier-ignore
                if iszero(lt(subject, subjectSearchEnd)) { break }

                if iszero(lt(searchLength, 32)) {
                    // prettier-ignore
                    for { let h := keccak256(add(search, 0x20), searchLength) } 1 {} {
                        if iszero(shr(m, xor(mload(subject), s))) {
                            if eq(keccak256(subject, searchLength), h) {
                                result := sub(subject, subjectStart)
                                break
                            }
                        }
                        subject := add(subject, 1)
                        // prettier-ignore
                        if iszero(lt(subject, subjectSearchEnd)) { break }
                    }
                    break
                }
                // prettier-ignore
                for {} 1 {} {
                    if iszero(shr(m, xor(mload(subject), s))) {
                        result := sub(subject, subjectStart)
                        break
                    }
                    subject := add(subject, 1)
                    // prettier-ignore
                    if iszero(lt(subject, subjectSearchEnd)) { break }
                }
                break
            }
        }
    }

    /// @dev Returns the byte index of the first location of `search` in `subject`,
    /// searching from left to right.
    /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found.
    function indexOf(string memory subject, string memory search) internal pure returns (uint256 result) {
        result = indexOf(subject, search, 0);
    }

    /// @dev Returns the byte index of the first location of `search` in `subject`,
    /// searching from right to left, starting from `from`.
    /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found.
    function lastIndexOf(
        string memory subject,
        string memory search,
        uint256 from
    ) internal pure returns (uint256 result) {
        assembly {
            // prettier-ignore
            for {} 1 {} {
                let searchLength := mload(search)
                let fromMax := sub(mload(subject), searchLength)
                if iszero(gt(fromMax, from)) {
                    from := fromMax
                }
                if iszero(mload(search)) {
                    result := from
                    break
                }
                result := not(0) // Initialize to `NOT_FOUND`.

                let subjectSearchEnd := sub(add(subject, 0x20), 1)

                subject := add(add(subject, 0x20), from)
                // prettier-ignore
                if iszero(gt(subject, subjectSearchEnd)) { break }
                // As this function is not too often used,
                // we shall simply use keccak256 for smaller bytecode size.
                // prettier-ignore
                for { let h := keccak256(add(search, 0x20), searchLength) } 1 {} {
                    if eq(keccak256(subject, searchLength), h) {
                        result := sub(subject, add(subjectSearchEnd, 1))
                        break
                    }
                    subject := sub(subject, 1)
                    // prettier-ignore
                    if iszero(gt(subject, subjectSearchEnd)) { break }
                }
                break
            }
        }
    }

    /// @dev Returns the index of the first location of `search` in `subject`,
    /// searching from right to left.
    /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found.
    function lastIndexOf(string memory subject, string memory search) internal pure returns (uint256 result) {
        result = lastIndexOf(subject, search, uint256(int256(-1)));
    }

    /// @dev Returns whether `subject` starts with `search`.
    function startsWith(string memory subject, string memory search) internal pure returns (bool result) {
        assembly {
            let searchLength := mload(search)
            // Just using keccak256 directly is actually cheaper.
            result := and(
                iszero(gt(searchLength, mload(subject))),
                eq(keccak256(add(subject, 0x20), searchLength), keccak256(add(search, 0x20), searchLength))
            )
        }
    }

    /// @dev Returns whether `subject` ends with `search`.
    function endsWith(string memory subject, string memory search) internal pure returns (bool result) {
        assembly {
            let searchLength := mload(search)
            let subjectLength := mload(subject)
            // Whether `search` is not longer than `subject`.
            let withinRange := iszero(gt(searchLength, subjectLength))
            // Just using keccak256 directly is actually cheaper.
            result := and(
                withinRange,
                eq(
                    keccak256(
                        // `subject + 0x20 + max(subjectLength - searchLength, 0)`.
                        add(add(subject, 0x20), mul(withinRange, sub(subjectLength, searchLength))),
                        searchLength
                    ),
                    keccak256(add(search, 0x20), searchLength)
                )
            )
        }
    }

    /// @dev Returns `subject` repeated `times`.
    function repeat(string memory subject, uint256 times) internal pure returns (string memory result) {
        assembly {
            let subjectLength := mload(subject)
            if iszero(or(iszero(times), iszero(subjectLength))) {
                subject := add(subject, 0x20)
                result := mload(0x40)
                let output := add(result, 0x20)
                // prettier-ignore
                for {} 1 {} {
                    // Copy the `subject` one word at a time.
                    // prettier-ignore
                    for { let o := 0 } 1 {} {
                        mstore(add(output, o), mload(add(subject, o)))
                        o := add(o, 0x20)
                        // prettier-ignore
                        if iszero(lt(o, subjectLength)) { break }
                    }
                    output := add(output, subjectLength)
                    times := sub(times, 1)
                    // prettier-ignore
                    if iszero(times) { break }
                }
                // Zeroize the slot after the string.
                mstore(output, 0)
                // Store the length.
                let resultLength := sub(output, add(result, 0x20))
                mstore(result, resultLength)
                // Allocate memory for the length and the bytes,
                // rounded up to a multiple of 32.
                mstore(0x40, add(result, and(add(resultLength, 63), not(31))))
            }
        }
    }

    /// @dev Returns a copy of `subject` sliced from `start` to `end` (exclusive).
    /// `start` and `end` are byte offsets.
    function slice(
        string memory subject,
        uint256 start,
        uint256 end
    ) internal pure returns (string memory result) {
        assembly {
            let subjectLength := mload(subject)
            if iszero(gt(subjectLength, end)) {
                end := subjectLength
            }
            if iszero(gt(subjectLength, start)) {
                start := subjectLength
            }
            if lt(start, end) {
                result := mload(0x40)
                let resultLength := sub(end, start)
                mstore(result, resultLength)
                subject := add(subject, start)
                // Copy the `subject` one word at a time, backwards.
                // prettier-ignore
                for { let o := and(add(resultLength, 31), not(31)) } 1 {} {
                    mstore(add(result, o), mload(add(subject, o)))
                    o := sub(o, 0x20)
                    // prettier-ignore
                    if iszero(o) { break }
                }
                // Zeroize the slot after the string.
                mstore(add(add(result, 0x20), resultLength), 0)
                // Allocate memory for the length and the bytes,
                // rounded up to a multiple of 32.
                mstore(0x40, add(result, and(add(resultLength, 63), not(31))))
            }
        }
    }

    /// @dev Returns a copy of `subject` sliced from `start` to the end of the string.
    /// `start` is a byte offset.
    function slice(string memory subject, uint256 start) internal pure returns (string memory result) {
        result = slice(subject, start, uint256(int256(-1)));
    }

    /// @dev Returns all the indices of `search` in `subject`.
    /// The indices are byte offsets.
    function indicesOf(string memory subject, string memory search) internal pure returns (uint256[] memory result) {
        assembly {
            let subjectLength := mload(subject)
            let searchLength := mload(search)

            if iszero(gt(searchLength, subjectLength)) {
                subject := add(subject, 0x20)
                search := add(search, 0x20)
                result := add(mload(0x40), 0x20)

                let subjectStart := subject
                let subjectSearchEnd := add(sub(add(subject, subjectLength), searchLength), 1)
                let h := 0
                if iszero(lt(searchLength, 32)) {
                    h := keccak256(search, searchLength)
                }
                let m := shl(3, sub(32, and(searchLength, 31)))
                let s := mload(search)
                // prettier-ignore
                for {} 1 {} {
                    let t := mload(subject)
                    // Whether the first `searchLength % 32` bytes of 
                    // `subject` and `search` matches.
                    if iszero(shr(m, xor(t, s))) {
                        if h {
                            if iszero(eq(keccak256(subject, searchLength), h)) {
                                subject := add(subject, 1)
                                // prettier-ignore
                                if iszero(lt(subject, subjectSearchEnd)) { break }
                                continue
                            }
                        }
                        // Append to `result`.
                        mstore(result, sub(subject, subjectStart))
                        result := add(result, 0x20)
                        // Advance `subject` by `searchLength`.
                        subject := add(subject, searchLength)
                        if searchLength {
                            // prettier-ignore
                            if iszero(lt(subject, subjectSearchEnd)) { break }
                            continue
                        }
                    }
                    subject := add(subject, 1)
                    // prettier-ignore
                    if iszero(lt(subject, subjectSearchEnd)) { break }
                }
                let resultEnd := result
                // Assign `result` to the free memory pointer.
                result := mload(0x40)
                // Store the length of `result`.
                mstore(result, shr(5, sub(resultEnd, add(result, 0x20))))
                // Allocate memory for result.
                // We allocate one more word, so this array can be recycled for {split}.
                mstore(0x40, add(resultEnd, 0x20))
            }
        }
    }

    /// @dev Returns a arrays of strings based on the `delimiter` inside of the `subject` string.
    function split(string memory subject, string memory delimiter) internal pure returns (string[] memory result) {
        uint256[] memory indices = indicesOf(subject, delimiter);
        assembly {
            if mload(indices) {
                let indexPtr := add(indices, 0x20)
                let indicesEnd := add(indexPtr, shl(5, add(mload(indices), 1)))
                mstore(sub(indicesEnd, 0x20), mload(subject))
                mstore(indices, add(mload(indices), 1))
                let prevIndex := 0
                // prettier-ignore
                for {} 1 {} {
                    let index := mload(indexPtr)
                    mstore(indexPtr, 0x60)                        
                    if iszero(eq(index, prevIndex)) {
                        let element := mload(0x40)
                        let elementLength := sub(index, prevIndex)
                        mstore(element, elementLength)
                        // Copy the `subject` one word at a time, backwards.
                        // prettier-ignore
                        for { let o := and(add(elementLength, 31), not(31)) } 1 {} {
                            mstore(add(element, o), mload(add(add(subject, prevIndex), o)))
                            o := sub(o, 0x20)
                            // prettier-ignore
                            if iszero(o) { break }
                        }
                        // Zeroize the slot after the string.
                        mstore(add(add(element, 0x20), elementLength), 0)
                        // Allocate memory for the length and the bytes,
                        // rounded up to a multiple of 32.
                        mstore(0x40, add(element, and(add(elementLength, 63), not(31))))
                        // Store the `element` into the array.
                        mstore(indexPtr, element)                        
                    }
                    prevIndex := add(index, mload(delimiter))
                    indexPtr := add(indexPtr, 0x20)
                    // prettier-ignore
                    if iszero(lt(indexPtr, indicesEnd)) { break }
                }
                result := indices
                if iszero(mload(delimiter)) {
                    result := add(indices, 0x20)
                    mstore(result, sub(mload(indices), 2))
                }
            }
        }
    }

    /// @dev Returns a concatenated string of `a` and `b`.
    /// Cheaper than `string.concat()` and does not de-align the free memory pointer.
    function concat(string memory a, string memory b) internal pure returns (string memory result) {
        assembly {
            result := mload(0x40)
            let aLength := mload(a)
            // Copy `a` one word at a time, backwards.
            // prettier-ignore
            for { let o := and(add(mload(a), 32), not(31)) } 1 {} {
                mstore(add(result, o), mload(add(a, o)))
                o := sub(o, 0x20)
                // prettier-ignore
                if iszero(o) { break }
            }
            let bLength := mload(b)
            let output := add(result, mload(a))
            // Copy `b` one word at a time, backwards.
            // prettier-ignore
            for { let o := and(add(bLength, 32), not(31)) } 1 {} {
                mstore(add(output, o), mload(add(b, o)))
                o := sub(o, 0x20)
                // prettier-ignore
                if iszero(o) { break }
            }
            let totalLength := add(aLength, bLength)
            let last := add(add(result, 0x20), totalLength)
            // Zeroize the slot after the string.
            mstore(last, 0)
            // Stores the length.
            mstore(result, totalLength)
            // Allocate memory for the length and the bytes,
            // rounded up to a multiple of 32.
            mstore(0x40, and(add(last, 31), not(31)))
        }
    }

    /// @dev Packs a single string with its length into a single word.
    /// Returns `bytes32(0)` if the length is zero or greater than 31.
    function packOne(string memory a) internal pure returns (bytes32 result) {
        assembly {
            // We don't need to zero right pad the string,
            // since this is our own custom non-standard packing scheme.
            result := mul(
                // Load the length and the bytes.
                mload(add(a, 0x1f)),
                // `length != 0 && length < 32`. Abuses underflow.
                // Assumes that the length is valid and within the block gas limit.
                lt(sub(mload(a), 1), 0x1f)
            )
        }
    }

    /// @dev Unpacks a string packed using {packOne}.
    /// Returns the empty string if `packed` is `bytes32(0)`.
    /// If `packed` is not an output of {packOne}, the output behaviour is undefined.
    function unpackOne(bytes32 packed) internal pure returns (string memory result) {
        assembly {
            // Grab the free memory pointer.
            result := mload(0x40)
            // Allocate 2 words (1 for the length, 1 for the bytes).
            mstore(0x40, add(result, 0x40))
            // Zeroize the length slot.
            mstore(result, 0)
            // Store the length and bytes.
            mstore(add(result, 0x1f), packed)
            // Right pad with zeroes.
            mstore(add(add(result, 0x20), mload(result)), 0)
        }
    }

    /// @dev Packs two strings with their lengths into a single word.
    /// Returns `bytes32(0)` if combined length is zero or greater than 30.
    function packTwo(string memory a, string memory b) internal pure returns (bytes32 result) {
        assembly {
            let aLength := mload(a)
            // We don't need to zero right pad the strings,
            // since this is our own custom non-standard packing scheme.
            result := mul(
                // Load the length and the bytes of `a` and `b`.
                or(shl(shl(3, sub(0x1f, aLength)), mload(add(a, aLength))), mload(sub(add(b, 0x1e), aLength))),
                // `totalLength != 0 && totalLength < 31`. Abuses underflow.
                // Assumes that the lengths are valid and within the block gas limit.
                lt(sub(add(aLength, mload(b)), 1), 0x1e)
            )
        }
    }

    /// @dev Unpacks strings packed using {packTwo}.
    /// Returns the empty strings if `packed` is `bytes32(0)`.
    /// If `packed` is not an output of {packTwo}, the output behaviour is undefined.
    function unpackTwo(bytes32 packed) internal pure returns (string memory resultA, string memory resultB) {
        assembly {
            // Grab the free memory pointer.
            resultA := mload(0x40)
            resultB := add(resultA, 0x40)
            // Allocate 2 words for each string (1 for the length, 1 for the byte). Total 4 words.
            mstore(0x40, add(resultB, 0x40))
            // Zeroize the length slots.
            mstore(resultA, 0)
            mstore(resultB, 0)
            // Store the lengths and bytes.
            mstore(add(resultA, 0x1f), packed)
            mstore(add(resultB, 0x1f), mload(add(add(resultA, 0x20), mload(resultA))))
            // Right pad with zeroes.
            mstore(add(add(resultA, 0x20), mload(resultA)), 0)
            mstore(add(add(resultB, 0x20), mload(resultB)), 0)
        }
    }

    /// @dev Directly returns `a` without copying.
    function directReturn(string memory a) internal pure {
        assembly {
            // Right pad with zeroes. Just in case the string is produced
            // by a method that doesn't zero right pad.
            mstore(add(add(a, 0x20), mload(a)), 0)
            // Store the return offset.
            // Assumes that the string does not start from the scratch space.
            mstore(sub(a, 0x20), 0x20)
            // End the transaction, returning the string.
            return(sub(a, 0x20), add(mload(a), 0x40))
        }
    }
}

File 8 of 20 : Context.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @title Context
/// @notice Overridable context for meta-transactions
/// @author OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts)
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

File 9 of 20 : Initializable.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {s as erc1967ds} from "../proxy/ERC1967Proxy.sol";

// ------------- errors

error ProxyCallRequired();
error AlreadyInitialized();

/// @title Initializable
/// @author phaze (https://github.com/0xPhaze/UDS)
/// @dev functions using the `initializer` modifier are only callable during proxy deployment
/// @dev functions using the `reinitializer` modifier are only callable through a proxy
/// @dev and only before a proxy upgrade migration has completed
/// @dev (only when `upgradeToAndCall`'s `initCalldata` is being executed)
/// @dev allows re-initialization during upgrades
abstract contract Initializable {
    address private immutable __implementation = address(this);

    /* ------------- modifier ------------- */

    modifier initializer() {
        if (address(this).code.length != 0) revert AlreadyInitialized();
        _;
    }

    modifier reinitializer() {
        if (address(this) == __implementation) revert ProxyCallRequired();
        if (erc1967ds().implementation == __implementation) revert AlreadyInitialized();
        _;
    }
}

File 10 of 20 : ERC721M.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {EIP712PermitUDS} from "UDS/auth/EIP712PermitUDS.sol";
import {UserDataOps, TokenDataOps} from "./ERC721MLibrary.sol";

// ------------- storage

struct ERC721MStorage {
    string name;
    string symbol;
    uint256 totalSupply;
    mapping(address => uint256) userData;
    mapping(uint256 => uint256) tokenData;
    mapping(uint256 => address) getApproved;
    mapping(address => mapping(address => bool)) isApprovedForAll;
}

bytes32 constant DIAMOND_STORAGE_ERC721M_LOCKABLE = keccak256("diamond.storage.erc721m.lockable");

function s() pure returns (ERC721MStorage storage diamondStorage) {
    bytes32 slot = DIAMOND_STORAGE_ERC721M_LOCKABLE;
    assembly { diamondStorage.slot := slot } // prettier-ignore
}

// ------------- errors

error IncorrectOwner();
error TokenIdUnlocked();
error NonexistentToken();
error MintZeroQuantity();
error MintToZeroAddress();
error TransferFromInvalidTo();
error TransferToZeroAddress();
error CallerNotOwnerNorApproved();
error TransferFromIncorrectOwner();
error TransferToNonERC721Receiver();

/// @title ERC721M (Integrated Token Locking)
/// @author phaze (https://github.com/0xPhaze/ERC721M)
/// @author modified from ERC721A (https://github.com/chiru-labs/ERC721A)
/// @author modified from Solmate (https://github.com/Rari-Capital/solmate)
/// @notice Integrates EIP712Permit
abstract contract ERC721M is EIP712PermitUDS {
    using UserDataOps for uint256;
    using TokenDataOps for uint256;

    event Transfer(address indexed from, address indexed to, uint256 indexed id);
    event Approval(address indexed owner, address indexed spender, uint256 indexed id);
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    uint256 constant startingIndex = 1;

    constructor(string memory name_, string memory symbol_) {
        __ERC721_init(name_, symbol_);
    }

    /* ------------- init ------------- */

    function __ERC721_init(string memory name_, string memory symbol_) internal {
        s().name = name_;
        s().symbol = symbol_;
    }

    /* ------------- virtual ------------- */

    function tokenURI(uint256 id) external view virtual returns (string memory);

    /* ------------- view ------------- */

    function name() external view virtual returns (string memory) {
        return s().name;
    }

    function symbol() external view virtual returns (string memory) {
        return s().symbol;
    }

    function balanceOf(address user) public view virtual returns (uint256) {
        return s().userData[user].balance();
    }

    function getApproved(uint256 id) external view virtual returns (address) {
        return s().getApproved[id];
    }

    function isApprovedForAll(address owner, address spender) external view virtual returns (bool) {
        return s().isApprovedForAll[owner][spender];
    }

    function ownerOf(uint256 id) public view virtual returns (address) {
        return _tokenDataOf(id).owner();
    }

    function totalSupply() public view virtual returns (uint256) {
        return s().totalSupply;
    }

    function getAux(uint256 id) public view returns (uint256) {
        return _tokenDataOf(id).aux();
    }

    function getLockStart(uint256 id) public view returns (uint256) {
        return _tokenDataOf(id).tokenLockStart();
    }

    function numMinted(address user) public view virtual returns (uint256) {
        return s().userData[user].numMinted();
    }

    function numLocked(address user) public view virtual returns (uint256) {
        return s().userData[user].numLocked();
    }

    function getLockStart(address user) public view virtual returns (uint256) {
        return s().userData[user].userLockStart();
    }

    function trueOwnerOf(uint256 id) public view virtual returns (address) {
        return _tokenDataOf(id).trueOwner();
    }

    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return
            interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
            interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
            interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
    }

    /* ------------- public ------------- */

    function approve(address spender, uint256 id) public virtual {
        address owner = _tokenDataOf(id).owner();

        if (msg.sender != owner && !s().isApprovedForAll[owner][msg.sender]) revert CallerNotOwnerNorApproved();

        s().getApproved[id] = spender;

        emit Approval(owner, spender, id);
    }

    function setApprovalForAll(address operator, bool approved) public virtual {
        s().isApprovedForAll[msg.sender][operator] = approved;

        emit ApprovalForAll(msg.sender, operator, approved);
    }

    function _isApprovedOrOwner(address from, uint256 id) private view returns (bool) {
        return (msg.sender == from || s().isApprovedForAll[from][msg.sender] || s().getApproved[id] == msg.sender);
    }

    function transferFrom(
        address from,
        address to,
        uint256 id
    ) public virtual {
        if (to == address(this)) revert TransferFromInvalidTo();
        if (to == address(0)) revert TransferToZeroAddress();

        uint256 tokenData = _tokenDataOf(id);

        bool isApprovedOrOwner = (msg.sender == from ||
            s().isApprovedForAll[from][msg.sender] ||
            s().getApproved[id] == msg.sender);

        if (!isApprovedOrOwner) revert CallerNotOwnerNorApproved();
        if (tokenData.owner() != from) revert TransferFromIncorrectOwner();

        delete s().getApproved[id];

        unchecked {
            _ensureTokenDataSet(id + 1, tokenData);
        }

        s().tokenData[id] = tokenData.setOwner(to).flagNextTokenDataSet();

        s().userData[to] = s().userData[to].increaseBalance(1);
        s().userData[from] = s().userData[from].decreaseBalance(1);

        emit Transfer(from, to, id);
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id
    ) public virtual {
        safeTransferFrom(from, to, id, "");
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        bytes memory data
    ) public virtual {
        transferFrom(from, to, id);
        if (
            to.code.length != 0 &&
            IERC721Receiver(to).onERC721Received(msg.sender, from, id, data) !=
            IERC721Receiver(to).onERC721Received.selector
        ) revert TransferToNonERC721Receiver();
    }

    // EIP-4494 permit; differs from the current EIP
    function permit(
        address owner,
        address operator,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s_
    ) public virtual {
        _usePermit(owner, operator, 1, deadline, v, r, s_);

        s().isApprovedForAll[owner][operator] = true;

        emit ApprovalForAll(owner, operator, true);
    }

    /* ------------- internal ------------- */

    function _exists(uint256 id) internal view virtual returns (bool) {
        return startingIndex <= id && id < _nextTokenId();
    }

    function _nextTokenId() internal view virtual returns (uint256) {
        return startingIndex + totalSupply();
    }

    function _increaseTotalSupply(uint256 amount) internal virtual {
        if (amount != 0) s().totalSupply = _nextTokenId() + amount - 1;
    }

    function _tokenDataOf(uint256 id) internal view virtual returns (uint256 out) {
        if (!_exists(id)) revert NonexistentToken();

        unchecked {
            uint256 tokenData;

            for (uint256 curr = id; ; curr--) {
                tokenData = s().tokenData[curr];

                if (tokenData != 0) return tokenData;
            }
        }
    }

    function _ensureTokenDataSet(uint256 id, uint256 tokenData) internal virtual {
        if (!tokenData.nextTokenDataSet() && s().tokenData[id] == 0 && _exists(id)) s().tokenData[id] = tokenData;
    }

    function _mint(address to, uint256 quantity) internal virtual {
        _mintAndLock(to, quantity, false, 0);
    }

    function _mint(
        address to,
        uint256 quantity,
        uint48 auxData
    ) internal virtual {
        _mintAndLock(to, quantity, false, auxData);
    }

    function _mintAndLock(
        address to,
        uint256 quantity,
        bool lock
    ) internal virtual {
        _mintAndLock(to, quantity, lock, 0);
    }

    function _mintAndLock(
        address to,
        uint256 quantity,
        bool lock,
        uint48 auxData
    ) internal virtual {
        unchecked {
            if (quantity == 0) revert MintZeroQuantity();
            if (to == address(0)) revert MintToZeroAddress();

            uint256 startTokenId = _nextTokenId();
            uint256 tokenData = uint256(uint160(to)).setAux(auxData);
            uint256 userData = s().userData[to];

            // don't have to care about next token data if only minting one
            if (quantity == 1) tokenData = tokenData.flagNextTokenDataSet();
            if (lock) {
                tokenData = tokenData.setConsecutiveLocked().lock();

                userData = userData.increaseNumLocked(quantity).setUserLockStart(block.timestamp);

                for (uint256 i; i < quantity; ++i) {
                    emit Transfer(address(0), to, startTokenId + i);
                    emit Transfer(to, address(this), startTokenId + i);
                }
            } else {
                for (uint256 i; i < quantity; ++i) {
                    emit Transfer(address(0), to, startTokenId + i);
                }
            }

            s().userData[to] = userData.increaseNumMinted(quantity).increaseBalance(quantity);
            s().tokenData[startTokenId] = tokenData;

            _increaseTotalSupply(quantity);
        }
    }

    function _setAux(uint256 id, uint48 aux) internal virtual {
        uint256 tokenData = _tokenDataOf(id);

        unchecked {
            _ensureTokenDataSet(id + 1, tokenData);
        }

        s().tokenData[id] = tokenData.setAux(aux);
    }

    function _lock(address from, uint256 id) internal virtual {
        uint256 tokenData = _tokenDataOf(id);

        bool isApprovedOrOwner = (msg.sender == from ||
            s().isApprovedForAll[from][msg.sender] ||
            s().getApproved[id] == msg.sender);

        if (!isApprovedOrOwner) revert CallerNotOwnerNorApproved();
        if (tokenData.owner() != from) revert IncorrectOwner();

        delete s().getApproved[id];

        unchecked {
            _ensureTokenDataSet(id + 1, tokenData);
        }

        s().tokenData[id] = tokenData.lock().unsetConsecutiveLocked().flagNextTokenDataSet();
        s().userData[from] = s().userData[from].increaseNumLocked(1).setUserLockStart(block.timestamp);

        emit Transfer(from, address(this), id);
    }

    function _unlock(address from, uint256 id) internal virtual {
        uint256 tokenData = _tokenDataOf(id);

        bool isApprovedOrOwner = (msg.sender == from ||
            s().isApprovedForAll[from][msg.sender] ||
            s().getApproved[id] == msg.sender);

        if (!isApprovedOrOwner) revert CallerNotOwnerNorApproved();
        if (!tokenData.locked()) revert TokenIdUnlocked();
        if (tokenData.trueOwner() != from) revert IncorrectOwner();

        // if isConsecutiveLocked flag is set, we need to make sure that next tokenData is set
        // because tokenData in this case is implicit and needs to carry over
        if (tokenData.isConsecutiveLocked()) {
            unchecked {
                _ensureTokenDataSet(id + 1, tokenData);

                tokenData = tokenData.unsetConsecutiveLocked().flagNextTokenDataSet();
            }
        }

        s().tokenData[id] = tokenData.unlock();
        s().userData[from] = s().userData[from].decreaseNumLocked(1).setUserLockStart(block.timestamp);

        emit Transfer(address(this), from, id);
    }
}

interface IERC721Receiver {
    function onERC721Received(
        address operator,
        address from,
        uint256 id,
        bytes calldata data
    ) external returns (bytes4);
}

File 11 of 20 : ERC721MQuery.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../ERC721MLibrary.sol";
import {ERC721M, s} from "../ERC721M.sol";

/// @title ERC721M Query Extension
/// @author phaze (https://github.com/0xPhaze/ERC721M)
abstract contract ERC721MQuery is ERC721M {
    using UserDataOps for uint256;
    using TokenDataOps for uint256;

    /* ------------- O(n) read-only ------------- */

    function getOwnedIds(address user) external view returns (uint256[] memory) {
        return utils.getOwnedIds(s().tokenData, user, startingIndex, totalSupply());
    }

    function getLockedIds(address user) external view returns (uint256[] memory) {
        return utils.getLockedIds(s().tokenData, user, startingIndex, totalSupply());
    }

    function getUnlockedIds(address user) external view returns (uint256[] memory) {
        return utils.getUnlockedIds(s().tokenData, user, startingIndex, totalSupply());
    }

    function totalNumLocked() external view returns (uint256) {
        uint256 data;
        uint256 count;
        uint256 endIndex = _nextTokenId();
        uint256 currentData;

        unchecked {
            for (uint256 i = startingIndex; i < endIndex; ++i) {
                data = s().tokenData[i];
                if (data != 0) currentData = data;
                if (currentData.locked()) ++count;
            }
        }

        return count;
    }
}

/// @title ERC721M Query Utils
/// @author phaze (https://github.com/0xPhaze/ERC721M)
library utils {
    using TokenDataOps for uint256;

    function getOwnedIds(
        mapping(uint256 => uint256) storage tokenDataOf,
        address user,
        uint256 start,
        uint256 collectionSize
    ) internal view returns (uint256[] memory ids) {
        uint256 memPtr;

        assembly {
            ids := mload(0x40)
            memPtr := add(ids, 0x20)
        }

        unchecked {
            uint256 data;
            uint256 currentData;
            uint256 end = collectionSize + start;
            for (uint256 id = start; id < end; ++id) {
                data = tokenDataOf[id];
                if (data != 0) currentData = data;
                if (user == address(uint160(currentData))) {
                    assembly {
                        mstore(memPtr, id)
                        memPtr := add(memPtr, 0x20)
                    }
                }
            }
        }

        assembly {
            mstore(ids, shr(5, sub(sub(memPtr, ids), 0x20)))
            mstore(0x40, memPtr)
        }
    }

    function getLockedIds(
        mapping(uint256 => uint256) storage tokenDataOf,
        address user,
        uint256 start,
        uint256 collectionSize
    ) internal view returns (uint256[] memory ids) {
        uint256 memPtr;

        assembly {
            ids := mload(0x40)
            memPtr := add(ids, 0x20)
        }

        unchecked {
            uint256 data;
            uint256 currentData;
            uint256 end = collectionSize + start;
            for (uint256 id = start; id < end; ++id) {
                data = tokenDataOf[id];
                if (data != 0) currentData = data;
                if (user == address(uint160(currentData)) && currentData.locked()) {
                    assembly {
                        mstore(memPtr, id)
                        memPtr := add(memPtr, 0x20)
                    }
                }
            }
        }

        assembly {
            mstore(ids, shr(5, sub(sub(memPtr, ids), 0x20)))
            mstore(0x40, memPtr)
        }
    }

    function getUnlockedIds(
        mapping(uint256 => uint256) storage tokenDataOf,
        address user,
        uint256 start,
        uint256 collectionSize
    ) internal view returns (uint256[] memory ids) {
        uint256 memPtr;

        assembly {
            ids := mload(0x40)
            memPtr := add(ids, 0x20)
        }

        unchecked {
            uint256 data;
            uint256 currentData;
            uint256 end = collectionSize + start;
            for (uint256 id = start; id < end; ++id) {
                data = tokenDataOf[id];
                if (data != 0) currentData = data;
                if (user == address(uint160(currentData)) && !currentData.locked()) {
                    assembly {
                        mstore(memPtr, id)
                        memPtr := add(memPtr, 0x20)
                    }
                }
            }
        }

        assembly {
            mstore(ids, shr(5, sub(sub(memPtr, ids), 0x20)))
            mstore(0x40, memPtr)
        }
    }
}

File 12 of 20 : FxERC721Root.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {FxBaseRootTunnel} from "./base/FxBaseRootTunnel.sol";

bytes4 constant REGISTER_ERC721_IDS_SELECTOR = bytes4(keccak256("registerERC721IdsWithChild(address,uint256[])"));
bytes4 constant DEREGISTER_ERC721_IDS_SELECTOR = bytes4(keccak256("deregisterERC721IdsWithChild(uint256[])"));

/// @title ERC721 FxRootTunnel
/// @author phaze (https://github.com/0xPhaze/fx-contracts)
abstract contract FxERC721Root is FxBaseRootTunnel {
    constructor(address checkpointManager, address fxRoot) FxBaseRootTunnel(checkpointManager, fxRoot) {}

    /* ------------- virtual ------------- */

    function _authorizeTunnelController() internal virtual override;

    /* ------------- internal ------------- */

    function _registerERC721IdsWithChild(address to, uint256[] calldata ids) internal virtual {
        _sendMessageToChild(abi.encodeWithSelector(REGISTER_ERC721_IDS_SELECTOR, to, ids));
    }

    function _registerERC721IdsWithChildMem(address to, uint256[] memory ids) internal virtual {
        _sendMessageToChild(abi.encodeWithSelector(REGISTER_ERC721_IDS_SELECTOR, to, ids));
    }
}

File 13 of 20 : EIP712PermitUDS.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// ------------- storage

bytes32 constant DIAMOND_STORAGE_EIP_712_PERMIT = keccak256("diamond.storage.eip.712.permit");

function s() pure returns (EIP2612DS storage diamondStorage) {
    bytes32 slot = DIAMOND_STORAGE_EIP_712_PERMIT;
    assembly { diamondStorage.slot := slot } // prettier-ignore
}

struct EIP2612DS {
    mapping(address => uint256) nonces;
}

// ------------- errors

error InvalidSigner();
error DeadlineExpired();

/// @title EIP712Permit (Upgradeable Diamond Storage)
/// @author phaze (https://github.com/0xPhaze/UDS)
/// @author Modified from Solmate (https://github.com/Rari-Capital/solmate)
/// @dev `DOMAIN_SEPARATOR` needs to be re-computed every time
/// @dev for use with a proxy due to `address(this)`
abstract contract EIP712PermitUDS {
    EIP2612DS private __storageLayout; // storage layout for upgrade compatibility checks

    /* ------------- public ------------- */

    function nonces(address owner) public view returns (uint256) {
        return s().nonces[owner];
    }

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

    /* ------------- internal ------------- */

    function _usePermit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v_,
        bytes32 r_,
        bytes32 s_
    ) internal virtual {
        if (deadline < block.timestamp) revert DeadlineExpired();

        unchecked {
            uint256 nonce = s().nonces[owner]++;

            address recovered = 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,
                                nonce,
                                deadline
                            )
                        )
                    )
                ),
                v_,
                r_,
                s_
            );

            if (recovered == address(0) || recovered != owner) revert InvalidSigner();
        }
    }
}

File 14 of 20 : ERC1967Proxy.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// ------------- storage

// keccak256("eip1967.proxy.implementation") - 1
bytes32 constant ERC1967_PROXY_STORAGE_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

function s() pure returns (ERC1967UpgradeDS storage diamondStorage) {
    assembly { diamondStorage.slot := ERC1967_PROXY_STORAGE_SLOT } // prettier-ignore
}

struct ERC1967UpgradeDS {
    address implementation;
}

// ------------- errors

error InvalidUUID();
error NotAContract();

/// @title ERC1967
/// @author phaze (https://github.com/0xPhaze/UDS)
abstract contract ERC1967 {
    event Upgraded(address indexed implementation);

    function _upgradeToAndCall(address logic, bytes memory data) internal {
        if (logic.code.length == 0) revert NotAContract();

        if (ERC1822(logic).proxiableUUID() != ERC1967_PROXY_STORAGE_SLOT) revert InvalidUUID();

        if (data.length != 0) {
            (bool success, ) = logic.delegatecall(data);

            if (!success) {
                assembly {
                    returndatacopy(0, 0, returndatasize())
                    revert(0, returndatasize())
                }
            }
        }

        s().implementation = logic;

        emit Upgraded(logic);
    }
}

/// @title Minimal ERC1967Proxy
/// @author phaze (https://github.com/0xPhaze/UDS)
contract ERC1967Proxy is ERC1967 {
    constructor(address logic, bytes memory data) payable {
        _upgradeToAndCall(logic, data);
    }

    fallback() external payable {
        assembly {
            calldatacopy(0, 0, calldatasize())

            let success := delegatecall(gas(), sload(ERC1967_PROXY_STORAGE_SLOT), 0, calldatasize(), 0, 0)

            returndatacopy(0, 0, returndatasize())

            if success {
                return(0, returndatasize())
            }

            revert(0, returndatasize())
        }
    }
}

/// @title ERC1822
/// @author phaze (https://github.com/0xPhaze/UDS)
abstract contract ERC1822 {
    function proxiableUUID() external view virtual returns (bytes32);
}

File 15 of 20 : ERC721MLibrary.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @notice Library used for bitmap manipulation for ERC721M
/// @author phaze (https://github.com/0xPhaze/ERC721M)
library UserDataOps {
    /* ------------- balance: [0, 20) ------------- */

    function balance(uint256 userData) internal pure returns (uint256) {
        return userData & 0xFFFFF;
    }

    function increaseBalance(uint256 userData, uint256 amount) internal pure returns (uint256) {
        unchecked {
            return userData + amount;
        }
    }

    function decreaseBalance(uint256 userData, uint256 amount) internal pure returns (uint256) {
        unchecked {
            return userData - amount;
        }
    }

    /* ------------- numMinted: [20, 40) ------------- */

    function numMinted(uint256 userData) internal pure returns (uint256) {
        return (userData >> 20) & 0xFFFFF;
    }

    function increaseNumMinted(uint256 userData, uint256 amount) internal pure returns (uint256) {
        unchecked {
            return userData + (amount << 20);
        }
    }

    /* ------------- numLocked: [40, 60) ------------- */

    function numLocked(uint256 userData) internal pure returns (uint256) {
        return (userData >> 40) & 0xFFFFF;
    }

    function increaseNumLocked(uint256 userData, uint256 amount) internal pure returns (uint256) {
        unchecked {
            return userData + (amount << 40);
        }
    }

    function decreaseNumLocked(uint256 userData, uint256 amount) internal pure returns (uint256) {
        unchecked {
            return userData - (amount << 40);
        }
    }

    /* ------------- lockStart: [60, 100) ------------- */

    function userLockStart(uint256 userData) internal pure returns (uint256) {
        return (userData >> 60) & 0xFFFFFFFFFF;
    }

    function setUserLockStart(uint256 userData, uint256 timestamp) internal pure returns (uint256) {
        return (userData & ~uint256(0xFFFFFFFFFF << 60)) | (timestamp << 60);
    }

    // /* ------------- aux: [100, 256) ------------- */

    // function aux(uint256 userData) internal pure returns (uint256) {
    //     return (userData >> 100) & 0xFFFFFFFFFF;
    // }

    // function setAux(uint256 userData, uint256 aux_) internal pure returns (uint256) {
    //     return (userData & ~((uint256(1) << 100) - 1)) | (aux_ << 100);
    // }
}

library TokenDataOps {
    /// @dev Big question whether copy should transfer over data, such as,
    ///      aux data and timestamps
    function copy(uint256 tokenData) internal pure returns (uint256) {
        return tokenData;
    }

    // return tokenData & ((uint256(1) << (160 + (((tokenData >> 160) & 1) << 1))) - 1);
    /// ^ equivalent code:
    // function copy2(uint256 tokenData) internal pure returns (uint256) {
    //     uint256 copiedData = uint160(tokenData);
    //     if (isConsecutiveLocked(tokenData)) {
    //         copiedData = setConsecutiveLocked(copiedData);
    //         if (locked(tokenData)) copiedData = lock(copiedData);
    //     }
    //     return copiedData;
    // }

    /* ------------- owner: [0, 160) ------------- */

    function owner(uint256 tokenData) internal view returns (address) {
        return locked(tokenData) ? address(this) : trueOwner(tokenData);
    }

    function setOwner(uint256 tokenData, address owner_) internal pure returns (uint256) {
        return (tokenData & 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000) | uint160(owner_);
    }

    function trueOwner(uint256 tokenData) internal pure returns (address) {
        return address(uint160(tokenData));
    }

    /* ------------- consecutiveLock: [160, 161) ------------- */

    function isConsecutiveLocked(uint256 tokenData) internal pure returns (bool) {
        return ((tokenData >> 160) & uint256(1)) != 0;
    }

    function setConsecutiveLocked(uint256 tokenData) internal pure returns (uint256) {
        return tokenData | (uint256(1) << 160);
    }

    function unsetConsecutiveLocked(uint256 tokenData) internal pure returns (uint256) {
        return tokenData & ~(uint256(1) << 160);
    }

    /* ------------- locked: [161, 162) ------------- */

    function locked(uint256 tokenData) internal pure returns (bool) {
        return ((tokenData >> 161) & uint256(1)) != 0; // Note: this is not masked and can carry over when calling 'ownerOf'
    }

    function lock(uint256 tokenData) internal view returns (uint256) {
        return setTokenLockStart(tokenData, block.timestamp) | (uint256(1) << 161);
    }

    function unlock(uint256 tokenData) internal view returns (uint256) {
        return setTokenLockStart(tokenData, block.timestamp) & ~(uint256(1) << 161);
    }

    /* ------------- nextTokenDataSet: [162, 163) ------------- */

    function nextTokenDataSet(uint256 tokenData) internal pure returns (bool) {
        return ((tokenData >> 162) & uint256(1)) != 0;
    }

    function flagNextTokenDataSet(uint256 tokenData) internal pure returns (uint256) {
        return tokenData | (uint256(1) << 162); // nextTokenDatatSet flag (don't repeat the read/write)
    }

    /* ------------- lockStart: [168, 208) ------------- */

    function tokenLockStart(uint256 tokenData) internal pure returns (uint256) {
        return (tokenData >> 168) & 0xFFFFFFFFFF;
    }

    function setTokenLockStart(uint256 tokenData, uint256 timestamp) internal pure returns (uint256) {
        return (tokenData & ~uint256(0xFFFFFFFFFF << 168)) | (timestamp << 168);
    }

    /* ------------- aux: [208, 256) ------------- */

    function aux(uint256 tokenData) internal pure returns (uint256) {
        return tokenData >> 208;
    }

    function setAux(uint256 tokenData, uint256 auxData) internal pure returns (uint256) {
        return (tokenData & ~uint256(0xFFFFFFFFFFFF << 208)) | (auxData << 208);
    }
}

File 16 of 20 : FxBaseRootTunnel.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Merkle} from "../lib/Merkle.sol";
import {RLPReader} from "../lib/RLPReader.sol";
import {ExitPayloadReader} from "../lib/ExitPayloadReader.sol";
import {MerklePatriciaProof} from "../lib/MerklePatriciaProof.sol";

// ------------- interfaces

interface IFxStateSender {
    function sendMessageToChild(address _receiver, bytes calldata _data) external;
}

interface ICheckpointManager {
    function headerBlocks(uint256 headerNumber)
        external
        view
        returns (
            bytes32 root,
            uint256 start,
            uint256 end,
            uint256 createdAt,
            address proposer
        );
}

// ------------- storage

bytes32 constant DIAMOND_STORAGE_FX_BASE_ROOT_TUNNEL = keccak256("diamond.storage.fx.base.root.tunnel");

function s() pure returns (FxBaseRootTunnelDS storage diamondStorage) {
    bytes32 slot = DIAMOND_STORAGE_FX_BASE_ROOT_TUNNEL;
    assembly { diamondStorage.slot := slot } // prettier-ignore
}

struct FxBaseRootTunnelDS {
    address fxChildTunnel;
    mapping(bytes32 => bool) processedExits;
}

// ------------- errors

error FxChildUnset();
error InvalidHeader();
error InvalidSelector();
error InvalidReceiptProof();
error InvalidFxChildTunnel();
error ExitAlreadyProcessed();

abstract contract FxBaseRootTunnel {
    using RLPReader for RLPReader.RLPItem;
    using Merkle for bytes32;
    using ExitPayloadReader for bytes;
    using ExitPayloadReader for ExitPayloadReader.ExitPayload;
    using ExitPayloadReader for ExitPayloadReader.Log;
    using ExitPayloadReader for ExitPayloadReader.LogTopics;
    using ExitPayloadReader for ExitPayloadReader.Receipt;

    bytes32 private constant SEND_MESSAGE_EVENT_SELECTOR =
        0x8c5261668696ce22758910d05bab8f186d6eb247ceac2af2e82c7dc17669b036;

    IFxStateSender public immutable fxRoot;
    ICheckpointManager public immutable checkpointManager;

    constructor(address checkpointManager_, address fxRoot_) {
        checkpointManager = ICheckpointManager(checkpointManager_);
        fxRoot = IFxStateSender(fxRoot_);
    }

    /* ------------- virtual ------------- */

    function _authorizeTunnelController() internal virtual;

    /* ------------- view ------------- */

    function fxChildTunnel() public view virtual returns (address) {
        return s().fxChildTunnel;
    }

    function processedExits(bytes32 exitHash) public view virtual returns (bool) {
        return s().processedExits[exitHash];
    }

    function setFxChildTunnel(address fxChildTunnel_) public virtual {
        _authorizeTunnelController();

        s().fxChildTunnel = fxChildTunnel_;
    }

    /* ------------- internal ------------- */

    function _sendMessageToChild(bytes memory message) internal virtual {
        if (s().fxChildTunnel == address(0)) revert FxChildUnset();

        fxRoot.sendMessageToChild(s().fxChildTunnel, message);
    }

    /**
     * @notice receive message from  L2 to L1, validated by proof
     * @dev This function verifies if the transaction actually happened on child chain
     *
     * @param proofData RLP encoded data of the reference tx containing following list of fields
     *  0 - headerNumber - Checkpoint header block number containing the reference tx
     *  1 - blockProof - Proof that the block header (in the child chain) is a leaf in the submitted merkle root
     *  2 - blockNumber - Block number containing the reference tx on child chain
     *  3 - blockTime - Reference tx block time
     *  4 - txRoot - Transactions root of block
     *  5 - receiptRoot - Receipts root of block
     *  6 - receipt - Receipt of the reference transaction
     *  7 - receiptProof - Merkle proof of the reference receipt
     *  8 - branchMask - 32 bits denoting the path of receipt in merkle tree
     *  9 - receiptLogIndex - Log Index to read from the receipt
     */
    function _validateAndExtractMessage(bytes memory proofData) internal returns (bytes memory) {
        address childTunnel = s().fxChildTunnel;

        if (childTunnel == address(0)) revert FxChildUnset();

        ExitPayloadReader.ExitPayload memory payload = proofData.toExitPayload();

        bytes memory branchMaskBytes = payload.getBranchMaskAsBytes();
        uint256 blockNumber = payload.getBlockNumber();
        // checking if exit has already been processed
        // unique exit is identified using hash of (blockNumber, branchMask, receiptLogIndex)
        bytes32 exitHash = keccak256(
            abi.encodePacked(
                blockNumber,
                // first 2 nibbles are dropped while generating nibble array
                // this allows branch masks that are valid but bypass exitHash check (changing first 2 nibbles only)
                // so converting to nibble array and then hashing it
                MerklePatriciaProof._getNibbleArray(branchMaskBytes),
                payload.getReceiptLogIndex()
            )
        );

        if (s().processedExits[exitHash]) revert ExitAlreadyProcessed();

        s().processedExits[exitHash] = true;

        ExitPayloadReader.Receipt memory receipt = payload.getReceipt();
        ExitPayloadReader.Log memory log = receipt.getLog();

        // check child tunnel
        if (childTunnel != log.getEmitter()) revert InvalidFxChildTunnel();

        bytes32 receiptRoot = payload.getReceiptRoot();
        // verify receipt inclusion
        if (!MerklePatriciaProof.verify(receipt.toBytes(), branchMaskBytes, payload.getReceiptProof(), receiptRoot))
            revert InvalidReceiptProof();

        (bytes32 headerRoot, uint256 startBlock, , , ) = checkpointManager.headerBlocks(payload.getHeaderNumber());

        bytes32 leaf = keccak256(
            abi.encodePacked(blockNumber, payload.getBlockTime(), payload.getTxRoot(), receiptRoot)
        );

        if (!leaf.checkMembership(blockNumber - startBlock, headerRoot, payload.getBlockProof()))
            revert InvalidHeader();

        ExitPayloadReader.LogTopics memory topics = log.getTopics();

        if (bytes32(topics.getField(0).toUint()) != SEND_MESSAGE_EVENT_SELECTOR) revert InvalidSelector();

        // received message data
        bytes memory message = abi.decode(log.getData(), (bytes)); // event decodes params again, so decoding bytes to get message

        return message;
    }
}

File 17 of 20 : Merkle.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

library Merkle {
    function checkMembership(
        bytes32 leaf,
        uint256 index,
        bytes32 rootHash,
        bytes memory proof
    ) internal pure returns (bool) {
        require(proof.length % 32 == 0, "Invalid proof length");
        uint256 proofHeight = proof.length / 32;
        // Proof of size n means, height of the tree is n+1.
        // In a tree of height n+1, max #leafs possible is 2 ^ n
        require(index < 2**proofHeight, "Leaf index is too big");

        bytes32 proofElement;
        bytes32 computedHash = leaf;
        for (uint256 i = 32; i <= proof.length; i += 32) {
            assembly {
                proofElement := mload(add(proof, i))
            }

            if (index % 2 == 0) {
                computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
            } else {
                computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
            }

            index = index / 2;
        }
        return computedHash == rootHash;
    }
}

File 18 of 20 : RLPReader.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/*
 * @author Hamdi Allam [email protected]
 * Please reach out with any questions or concerns
 */
library RLPReader {
    uint8 constant STRING_SHORT_START = 0x80;
    uint8 constant STRING_LONG_START = 0xb8;
    uint8 constant LIST_SHORT_START = 0xc0;
    uint8 constant LIST_LONG_START = 0xf8;
    uint8 constant WORD_SIZE = 32;

    struct RLPItem {
        uint256 len;
        uint256 memPtr;
    }

    struct Iterator {
        RLPItem item; // Item that's being iterated over.
        uint256 nextPtr; // Position of the next item in the list.
    }

    /*
     * @dev Returns the next element in the iteration. Reverts if it has not next element.
     * @param self The iterator.
     * @return The next element in the iteration.
     */
    function next(Iterator memory self) internal pure returns (RLPItem memory) {
        require(hasNext(self));

        uint256 ptr = self.nextPtr;
        uint256 itemLength = _itemLength(ptr);
        self.nextPtr = ptr + itemLength;

        return RLPItem(itemLength, ptr);
    }

    /*
     * @dev Returns true if the iteration has more elements.
     * @param self The iterator.
     * @return true if the iteration has more elements.
     */
    function hasNext(Iterator memory self) internal pure returns (bool) {
        RLPItem memory item = self.item;
        return self.nextPtr < item.memPtr + item.len;
    }

    /*
     * @param item RLP encoded bytes
     */
    function toRlpItem(bytes memory item) internal pure returns (RLPItem memory) {
        uint256 memPtr;
        assembly {
            memPtr := add(item, 0x20)
        }

        return RLPItem(item.length, memPtr);
    }

    /*
     * @dev Create an iterator. Reverts if item is not a list.
     * @param self The RLP item.
     * @return An 'Iterator' over the item.
     */
    function iterator(RLPItem memory self) internal pure returns (Iterator memory) {
        require(isList(self));

        uint256 ptr = self.memPtr + _payloadOffset(self.memPtr);
        return Iterator(self, ptr);
    }

    /*
     * @param item RLP encoded bytes
     */
    function rlpLen(RLPItem memory item) internal pure returns (uint256) {
        return item.len;
    }

    /*
     * @param item RLP encoded bytes
     */
    function payloadLen(RLPItem memory item) internal pure returns (uint256) {
        return item.len - _payloadOffset(item.memPtr);
    }

    /*
     * @param item RLP encoded list in bytes
     */
    function toList(RLPItem memory item) internal pure returns (RLPItem[] memory) {
        require(isList(item));

        uint256 items = numItems(item);
        RLPItem[] memory result = new RLPItem[](items);

        uint256 memPtr = item.memPtr + _payloadOffset(item.memPtr);
        uint256 dataLen;
        for (uint256 i = 0; i < items; i++) {
            dataLen = _itemLength(memPtr);
            result[i] = RLPItem(dataLen, memPtr);
            memPtr = memPtr + dataLen;
        }

        return result;
    }

    // @return indicator whether encoded payload is a list. negate this function call for isData.
    function isList(RLPItem memory item) internal pure returns (bool) {
        if (item.len == 0) return false;

        uint8 byte0;
        uint256 memPtr = item.memPtr;
        assembly {
            byte0 := byte(0, mload(memPtr))
        }

        if (byte0 < LIST_SHORT_START) return false;
        return true;
    }

    /*
     * @dev A cheaper version of keccak256(toRlpBytes(item)) that avoids copying memory.
     * @return keccak256 hash of RLP encoded bytes.
     */
    function rlpBytesKeccak256(RLPItem memory item) internal pure returns (bytes32) {
        uint256 ptr = item.memPtr;
        uint256 len = item.len;
        bytes32 result;
        assembly {
            result := keccak256(ptr, len)
        }
        return result;
    }

    function payloadLocation(RLPItem memory item) internal pure returns (uint256, uint256) {
        uint256 offset = _payloadOffset(item.memPtr);
        uint256 memPtr = item.memPtr + offset;
        uint256 len = item.len - offset; // data length
        return (memPtr, len);
    }

    /*
     * @dev A cheaper version of keccak256(toBytes(item)) that avoids copying memory.
     * @return keccak256 hash of the item payload.
     */
    function payloadKeccak256(RLPItem memory item) internal pure returns (bytes32) {
        (uint256 memPtr, uint256 len) = payloadLocation(item);
        bytes32 result;
        assembly {
            result := keccak256(memPtr, len)
        }
        return result;
    }

    /** RLPItem conversions into data types **/

    // @returns raw rlp encoding in bytes
    function toRlpBytes(RLPItem memory item) internal pure returns (bytes memory) {
        bytes memory result = new bytes(item.len);
        if (result.length == 0) return result;

        uint256 ptr;
        assembly {
            ptr := add(0x20, result)
        }

        copy(item.memPtr, ptr, item.len);
        return result;
    }

    // any non-zero byte is considered true
    function toBoolean(RLPItem memory item) internal pure returns (bool) {
        require(item.len == 1);
        uint256 result;
        uint256 memPtr = item.memPtr;
        assembly {
            result := byte(0, mload(memPtr))
        }

        return result == 0 ? false : true;
    }

    function toAddress(RLPItem memory item) internal pure returns (address) {
        // 1 byte for the length prefix
        require(item.len == 21);

        return address(uint160(toUint(item)));
    }

    function toUint(RLPItem memory item) internal pure returns (uint256) {
        require(item.len > 0 && item.len <= 33);

        uint256 offset = _payloadOffset(item.memPtr);
        uint256 len = item.len - offset;

        uint256 result;
        uint256 memPtr = item.memPtr + offset;
        assembly {
            result := mload(memPtr)

            // shfit to the correct location if neccesary
            if lt(len, 32) {
                result := div(result, exp(256, sub(32, len)))
            }
        }

        return result;
    }

    // enforces 32 byte length
    function toUintStrict(RLPItem memory item) internal pure returns (uint256) {
        // one byte prefix
        require(item.len == 33);

        uint256 result;
        uint256 memPtr = item.memPtr + 1;
        assembly {
            result := mload(memPtr)
        }

        return result;
    }

    function toBytes(RLPItem memory item) internal pure returns (bytes memory) {
        require(item.len > 0);

        uint256 offset = _payloadOffset(item.memPtr);
        uint256 len = item.len - offset; // data length
        bytes memory result = new bytes(len);

        uint256 destPtr;
        assembly {
            destPtr := add(0x20, result)
        }

        copy(item.memPtr + offset, destPtr, len);
        return result;
    }

    /*
     * Private Helpers
     */

    // @return number of payload items inside an encoded list.
    function numItems(RLPItem memory item) private pure returns (uint256) {
        if (item.len == 0) return 0;

        uint256 count = 0;
        uint256 currPtr = item.memPtr + _payloadOffset(item.memPtr);
        uint256 endPtr = item.memPtr + item.len;
        while (currPtr < endPtr) {
            currPtr = currPtr + _itemLength(currPtr); // skip over an item
            count++;
        }

        return count;
    }

    // @return entire rlp item byte length
    function _itemLength(uint256 memPtr) private pure returns (uint256) {
        uint256 itemLen;
        uint256 byte0;
        assembly {
            byte0 := byte(0, mload(memPtr))
        }

        if (byte0 < STRING_SHORT_START) itemLen = 1;
        else if (byte0 < STRING_LONG_START) itemLen = byte0 - STRING_SHORT_START + 1;
        else if (byte0 < LIST_SHORT_START) {
            assembly {
                let byteLen := sub(byte0, 0xb7) // # of bytes the actual length is
                memPtr := add(memPtr, 1) // skip over the first byte
                /* 32 byte word size */
                let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to get the len
                itemLen := add(dataLen, add(byteLen, 1))
            }
        } else if (byte0 < LIST_LONG_START) {
            itemLen = byte0 - LIST_SHORT_START + 1;
        } else {
            assembly {
                let byteLen := sub(byte0, 0xf7)
                memPtr := add(memPtr, 1)

                let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to the correct length
                itemLen := add(dataLen, add(byteLen, 1))
            }
        }

        return itemLen;
    }

    // @return number of bytes until the data
    function _payloadOffset(uint256 memPtr) private pure returns (uint256) {
        uint256 byte0;
        assembly {
            byte0 := byte(0, mload(memPtr))
        }

        if (byte0 < STRING_SHORT_START) return 0;
        else if (byte0 < STRING_LONG_START || (byte0 >= LIST_SHORT_START && byte0 < LIST_LONG_START)) return 1;
        else if (byte0 < LIST_SHORT_START)
            // being explicit
            return byte0 - (STRING_LONG_START - 1) + 1;
        else return byte0 - (LIST_LONG_START - 1) + 1;
    }

    /*
     * @param src Pointer to source
     * @param dest Pointer to destination
     * @param len Amount of memory to copy from the source
     */
    function copy(
        uint256 src,
        uint256 dest,
        uint256 len
    ) private pure {
        if (len == 0) return;

        // copy as many word sizes as possible
        for (; len >= WORD_SIZE; len -= WORD_SIZE) {
            assembly {
                mstore(dest, mload(src))
            }

            src += WORD_SIZE;
            dest += WORD_SIZE;
        }

        if (len == 0) return;

        // left over bytes. Mask is used to remove unwanted bytes from the word
        uint256 mask = 256**(WORD_SIZE - len) - 1;

        assembly {
            let srcpart := and(mload(src), not(mask)) // zero out src
            let destpart := and(mload(dest), mask) // retrieve the bytes
            mstore(dest, or(destpart, srcpart))
        }
    }
}

File 19 of 20 : ExitPayloadReader.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

library ExitPayloadReader {
    using RLPReader for bytes;
    using RLPReader for RLPReader.RLPItem;

    uint8 constant WORD_SIZE = 32;

    struct ExitPayload {
        RLPReader.RLPItem[] data;
    }

    struct Receipt {
        RLPReader.RLPItem[] data;
        bytes raw;
        uint256 logIndex;
    }

    struct Log {
        RLPReader.RLPItem data;
        RLPReader.RLPItem[] list;
    }

    struct LogTopics {
        RLPReader.RLPItem[] data;
    }

    // copy paste of private copy() from RLPReader to avoid changing of existing contracts
    function copy(
        uint256 src,
        uint256 dest,
        uint256 len
    ) private pure {
        if (len == 0) return;

        // copy as many word sizes as possible
        for (; len >= WORD_SIZE; len -= WORD_SIZE) {
            assembly {
                mstore(dest, mload(src))
            }

            src += WORD_SIZE;
            dest += WORD_SIZE;
        }

        // left over bytes. Mask is used to remove unwanted bytes from the word
        uint256 mask = 256**(WORD_SIZE - len) - 1;
        assembly {
            let srcpart := and(mload(src), not(mask)) // zero out src
            let destpart := and(mload(dest), mask) // retrieve the bytes
            mstore(dest, or(destpart, srcpart))
        }
    }

    function toExitPayload(bytes memory data) internal pure returns (ExitPayload memory) {
        RLPReader.RLPItem[] memory payloadData = data.toRlpItem().toList();

        return ExitPayload(payloadData);
    }

    function getHeaderNumber(ExitPayload memory payload) internal pure returns (uint256) {
        return payload.data[0].toUint();
    }

    function getBlockProof(ExitPayload memory payload) internal pure returns (bytes memory) {
        return payload.data[1].toBytes();
    }

    function getBlockNumber(ExitPayload memory payload) internal pure returns (uint256) {
        return payload.data[2].toUint();
    }

    function getBlockTime(ExitPayload memory payload) internal pure returns (uint256) {
        return payload.data[3].toUint();
    }

    function getTxRoot(ExitPayload memory payload) internal pure returns (bytes32) {
        return bytes32(payload.data[4].toUint());
    }

    function getReceiptRoot(ExitPayload memory payload) internal pure returns (bytes32) {
        return bytes32(payload.data[5].toUint());
    }

    function getReceipt(ExitPayload memory payload) internal pure returns (Receipt memory receipt) {
        receipt.raw = payload.data[6].toBytes();
        RLPReader.RLPItem memory receiptItem = receipt.raw.toRlpItem();

        if (receiptItem.isList()) {
            // legacy tx
            receipt.data = receiptItem.toList();
        } else {
            // pop first byte before parsting receipt
            bytes memory typedBytes = receipt.raw;
            bytes memory result = new bytes(typedBytes.length - 1);
            uint256 srcPtr;
            uint256 destPtr;
            assembly {
                srcPtr := add(33, typedBytes)
                destPtr := add(0x20, result)
            }

            copy(srcPtr, destPtr, result.length);
            receipt.data = result.toRlpItem().toList();
        }

        receipt.logIndex = getReceiptLogIndex(payload);
        return receipt;
    }

    function getReceiptProof(ExitPayload memory payload) internal pure returns (bytes memory) {
        return payload.data[7].toBytes();
    }

    function getBranchMaskAsBytes(ExitPayload memory payload) internal pure returns (bytes memory) {
        return payload.data[8].toBytes();
    }

    function getBranchMaskAsUint(ExitPayload memory payload) internal pure returns (uint256) {
        return payload.data[8].toUint();
    }

    function getReceiptLogIndex(ExitPayload memory payload) internal pure returns (uint256) {
        return payload.data[9].toUint();
    }

    // Receipt methods
    function toBytes(Receipt memory receipt) internal pure returns (bytes memory) {
        return receipt.raw;
    }

    function getLog(Receipt memory receipt) internal pure returns (Log memory) {
        RLPReader.RLPItem memory logData = receipt.data[3].toList()[receipt.logIndex];
        return Log(logData, logData.toList());
    }

    // Log methods
    function getEmitter(Log memory log) internal pure returns (address) {
        return RLPReader.toAddress(log.list[0]);
    }

    function getTopics(Log memory log) internal pure returns (LogTopics memory) {
        return LogTopics(log.list[1].toList());
    }

    function getData(Log memory log) internal pure returns (bytes memory) {
        return log.list[2].toBytes();
    }

    function toRlpBytes(Log memory log) internal pure returns (bytes memory) {
        return log.data.toRlpBytes();
    }

    // LogTopics methods
    function getField(LogTopics memory topics, uint256 index) internal pure returns (RLPReader.RLPItem memory) {
        return topics.data[index];
    }
}

File 20 of 20 : MerklePatriciaProof.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

library MerklePatriciaProof {
    /*
     * @dev Verifies a merkle patricia proof.
     * @param value The terminating value in the trie.
     * @param encodedPath The path in the trie leading to value.
     * @param rlpParentNodes The rlp encoded stack of nodes.
     * @param root The root hash of the trie.
     * @return The boolean validity of the proof.
     */
    function verify(
        bytes memory value,
        bytes memory encodedPath,
        bytes memory rlpParentNodes,
        bytes32 root
    ) internal pure returns (bool) {
        RLPReader.RLPItem memory item = RLPReader.toRlpItem(rlpParentNodes);
        RLPReader.RLPItem[] memory parentNodes = RLPReader.toList(item);

        bytes memory currentNode;
        RLPReader.RLPItem[] memory currentNodeList;

        bytes32 nodeKey = root;
        uint256 pathPtr = 0;

        bytes memory path = _getNibbleArray(encodedPath);
        if (path.length == 0) {
            return false;
        }

        for (uint256 i = 0; i < parentNodes.length; i++) {
            if (pathPtr > path.length) {
                return false;
            }

            currentNode = RLPReader.toRlpBytes(parentNodes[i]);
            if (nodeKey != keccak256(currentNode)) {
                return false;
            }
            currentNodeList = RLPReader.toList(parentNodes[i]);

            if (currentNodeList.length == 17) {
                if (pathPtr == path.length) {
                    if (keccak256(RLPReader.toBytes(currentNodeList[16])) == keccak256(value)) {
                        return true;
                    } else {
                        return false;
                    }
                }

                uint8 nextPathNibble = uint8(path[pathPtr]);
                if (nextPathNibble > 16) {
                    return false;
                }
                nodeKey = bytes32(RLPReader.toUintStrict(currentNodeList[nextPathNibble]));
                pathPtr += 1;
            } else if (currentNodeList.length == 2) {
                uint256 traversed = _nibblesToTraverse(RLPReader.toBytes(currentNodeList[0]), path, pathPtr);
                if (pathPtr + traversed == path.length) {
                    //leaf node
                    if (keccak256(RLPReader.toBytes(currentNodeList[1])) == keccak256(value)) {
                        return true;
                    } else {
                        return false;
                    }
                }

                //extension node
                if (traversed == 0) {
                    return false;
                }

                pathPtr += traversed;
                nodeKey = bytes32(RLPReader.toUintStrict(currentNodeList[1]));
            } else {
                return false;
            }
        }

        return false;
    }

    function _nibblesToTraverse(
        bytes memory encodedPartialPath,
        bytes memory path,
        uint256 pathPtr
    ) private pure returns (uint256) {
        uint256 len = 0;
        // encodedPartialPath has elements that are each two hex characters (1 byte), but partialPath
        // and slicedPath have elements that are each one hex character (1 nibble)
        bytes memory partialPath = _getNibbleArray(encodedPartialPath);
        bytes memory slicedPath = new bytes(partialPath.length);

        // pathPtr counts nibbles in path
        // partialPath.length is a number of nibbles
        for (uint256 i = pathPtr; i < pathPtr + partialPath.length; i++) {
            bytes1 pathNibble = path[i];
            slicedPath[i - pathPtr] = pathNibble;
        }

        if (keccak256(partialPath) == keccak256(slicedPath)) {
            len = partialPath.length;
        } else {
            len = 0;
        }
        return len;
    }

    // bytes b must be hp encoded
    function _getNibbleArray(bytes memory b) internal pure returns (bytes memory) {
        bytes memory nibbles = "";
        if (b.length > 0) {
            uint8 offset;
            uint8 hpNibble = uint8(_getNthNibbleOfBytes(0, b));
            if (hpNibble == 1 || hpNibble == 3) {
                nibbles = new bytes(b.length * 2 - 1);
                bytes1 oddNibble = _getNthNibbleOfBytes(1, b);
                nibbles[0] = oddNibble;
                offset = 1;
            } else {
                nibbles = new bytes(b.length * 2 - 2);
                offset = 0;
            }

            for (uint256 i = offset; i < nibbles.length; i++) {
                nibbles[i] = _getNthNibbleOfBytes(i - offset + 2, b);
            }
        }
        return nibbles;
    }

    function _getNthNibbleOfBytes(uint256 n, bytes memory str) private pure returns (bytes1) {
        return bytes1(n % 2 == 0 ? uint8(str[n / 2]) / 0x10 : uint8(str[n / 2]) % 0x10);
    }
}

Settings
{
  "remappings": [
    "/=src/",
    "ERC721M/=lib/ERC721M/src/",
    "UDS/=lib/UDS/src/",
    "ds-test/=lib/ERC721M/lib/ds-test/src/",
    "forge-std/=lib/forge-std/src/",
    "futils/=lib/futils/src/",
    "fx-contracts/=lib/fx-contracts/src/",
    "fx-portal/=lib/ERC721M/lib/fx-portal/contracts/",
    "solady/=lib/solady/src/",
    "solmate/=lib/solmate/src/",
    "upgrade-scripts/=lib/upgrade-scripts/src/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 100000
  },
  "metadata": {
    "bytecodeHash": "none"
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "london",
  "libraries": {}
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"address","name":"checkpointManager","type":"address"},{"internalType":"address","name":"fxRoot","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"CallerNotOwner","type":"error"},{"inputs":[],"name":"CallerNotOwnerNorApproved","type":"error"},{"inputs":[],"name":"ContractCallNotAllowed","type":"error"},{"inputs":[],"name":"DeadlineExpired","type":"error"},{"inputs":[],"name":"ExceedsLimit","type":"error"},{"inputs":[],"name":"FxChildUnset","type":"error"},{"inputs":[],"name":"IncorrectOwner","type":"error"},{"inputs":[],"name":"IncorrectValue","type":"error"},{"inputs":[],"name":"InvalidPriceUnits","type":"error"},{"inputs":[],"name":"InvalidSignature","type":"error"},{"inputs":[],"name":"InvalidSigner","type":"error"},{"inputs":[],"name":"MaxSupplyLocked","type":"error"},{"inputs":[],"name":"MintToZeroAddress","type":"error"},{"inputs":[],"name":"MintZeroQuantity","type":"error"},{"inputs":[],"name":"NonexistentToken","type":"error"},{"inputs":[],"name":"PublicSaleNotActive","type":"error"},{"inputs":[],"name":"TimelockActive","type":"error"},{"inputs":[],"name":"TokenIdUnlocked","type":"error"},{"inputs":[],"name":"TransferFailed","type":"error"},{"inputs":[],"name":"TransferFromIncorrectOwner","type":"error"},{"inputs":[],"name":"TransferFromInvalidTo","type":"error"},{"inputs":[],"name":"TransferToNonERC721Receiver","type":"error"},{"inputs":[],"name":"TransferToZeroAddress","type":"error"},{"inputs":[],"name":"WhitelistNotActive","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"user","type":"address"}],"name":"FirstLegendaryRaffleEntered","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":false,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[],"name":"SaleStateUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"user","type":"address"}],"name":"SecondLegendaryRaffleEntered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"BRIDGE_RAFFLE_LOCK_DURATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_PER_WALLET","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PURCHASE_LIMIT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"users","type":"address[]"},{"internalType":"uint256","name":"quantity","type":"uint256"},{"internalType":"bool","name":"locked","type":"bool"}],"name":"airdrop","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"checkpointManager","outputs":[{"internalType":"contract ICheckpointManager","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fxChildTunnel","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fxRoot","outputs":[{"internalType":"contract IFxStateSender","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"gangOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"getAux","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"getLockStart","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getLockStart","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getLockedIds","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getOwnedIds","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getUnlockedIds","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"lockAndTransmit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lockMaxSupply","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"maxSupply","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxSupplyLocked","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"quantity","type":"uint256"},{"internalType":"bool","name":"lock","type":"bool"}],"name":"mint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"mintStart","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"numLocked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"numMinted","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"},{"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":"bytes32","name":"exitHash","type":"bytes32"}],"name":"processedExits","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"publicPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ERC20UDS","name":"token","type":"address"}],"name":"recoverToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"uri","type":"string"}],"name":"setBaseURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"fxChildTunnel_","type":"address"}],"name":"setFxChildTunnel","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"chunkIndices","type":"uint256[]"},{"internalType":"uint256[]","name":"chunks","type":"uint256[]"}],"name":"setGangs","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"value","type":"uint16"}],"name":"setMaxSupply","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"time","type":"uint32"}],"name":"setMintStart","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"postFix","type":"string"}],"name":"setPostFixURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"setPublicPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setSigner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"uri","type":"string"}],"name":"setUnrevealedURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"setWhitelistPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"supply","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalNumLocked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"trueOwnerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"unlockAndTransmit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"quantity","type":"uint256"},{"internalType":"bool","name":"lock","type":"bool"},{"internalType":"uint256","name":"limit","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"whitelistMint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"whitelistPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]

30608052610140604052600561010090815264173539b7b760d91b610120526004906200002d90826200036b565b5060405180606001604052806036815260200162005764603691396005906200005790826200036b565b503480156200006557600080fd5b506040516200579a3803806200579a833981016040819052620000889162000454565b604080518082018252601181527047616e67737461204d696365204369747960781b60208083019190915282518084019093526003835262474d4360e81b908301526001600160a01b0380851660c052831660a0529083838383620000ee8282620001b4565b505050505050620001046200021460201b60201c565b600280546b01000000000000000000000033027fff0000000000000000000000000000000000000000ffffffffffff0000ffffff90911617641a0a0000001790554260e0526200015b66ae153d89fe800062000267565b6002805460ff9290921669010000000000000000000260ff60481b1990921691909117905562000192668a8e4b1a3d800062000267565b6002600a6101000a81548160ff021916908360ff16021790555050506200048c565b7facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a95599620001e183826200036b565b507facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559a6200020f82826200036b565b505050565b303b15620002345760405162dc149f60e41b815260040160405180910390fd5b7f87917b04fc43108fc3d291ac961b425fe1ddcf80087b2cb7e3c48f3e9233ea3380546001600160a01b03191633179055565b60008066038d7ea4c680008306156200029357604051632411db2d60e11b815260040160405180910390fd5b5066038d7ea4c68000820460ff811115620002c157604051632411db2d60e11b815260040160405180910390fd5b92915050565b634e487b7160e01b600052604160045260246000fd5b600181811c90821680620002f257607f821691505b6020821081036200031357634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200020f57600081815260208120601f850160051c81016020861015620003425750805b601f850160051c820191505b8181101562000363578281556001016200034e565b505050505050565b81516001600160401b03811115620003875762000387620002c7565b6200039f81620003988454620002dd565b8462000319565b602080601f831160018114620003d75760008415620003be5750858301515b600019600386901b1c1916600185901b17855562000363565b600085815260208120601f198616915b828110156200040857888601518255948401946001909101908401620003e7565b5085821015620004275787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b80516001600160a01b03811681146200044f57600080fd5b919050565b600080604083850312156200046857600080fd5b620004738362000437565b9150620004836020840162000437565b90509250929050565b60805160a05160c05160e051615293620004d1600039600081816111b5015261171901526000610b17015260008181610c0201526145910152600050506152936000f3fe6080604052600436106103ad5760003560e01c806388486f86116101e7578063bb58131f1161010d578063de9b771f116100a0578063f2fde38b1161006f578063f2fde38b14610ccc578063fc1a1c3614610cec578063fca76c2614610d1c578063fe2c7fee14610d3157600080fd5b8063de9b771f14610bf0578063e7f371e614610c24578063e985e9c514610c37578063eec1cbb714610cac57600080fd5b8063c90af357116100dc578063c90af35714610b79578063ca9228a114610b99578063d5abeb0114610bb9578063d75e611014610bdb57600080fd5b8063bb58131f14610ae5578063c0857ba014610b05578063c627525514610b39578063c87b56dd14610b5957600080fd5b80639be65a6011610185578063a945bf8011610154578063a945bf8014610a56578063aea4e49e14610a85578063af436af314610aa5578063b88d4fde14610ac557600080fd5b80639be65a60146109dc578063a22cb465146109fc578063a742530814610a1c578063a8d0466c14610a3c57600080fd5b806395e1a8a5116101c157806395e1a8a514610970578063972c4928146109875780639871e2221461099c5780639888eb1b146109bc57600080fd5b806388486f86146109265780638da5cb5b1461094657806395d89b411461095b57600080fd5b80633644e515116102d757806367f68fac1161026a57806370a082311161023957806370a082311461086f578063717d57d31461088f5780637ecebe00146108af5780638456cb591461091157600080fd5b806367f68fac146107ef5780636c19e783146108025780636e8ba803146108225780636e9010381461084257600080fd5b806355f804b3116102a657806355f804b314610740578063607f2d42146107605780636352211e146107af57806364c6cbbd146107cf57600080fd5b80633644e515146106365780633ccfd60b146106eb57806342842e0e1461070057806348613c281461072057600080fd5b806311836f321161034f57806323b872dd1161031e57806323b872dd146105a6578063255e4685146105c6578063257f84ae14610601578063306c76791461061657600080fd5b806311836f321461051a57806318160ddd146105485780631fb161b81461056657806320fc7eb21461058657600080fd5b806306fdde031161038b57806306fdde031461043c578063081812fc1461045e578063095ea7b3146104e55780630f2cdd6c1461050557600080fd5b806301ffc9a7146103b2578063047fc9aa146103e757806306421c2f1461041a575b600080fd5b3480156103be57600080fd5b506103d26103cd3660046146fb565b610d51565b60405190151581526020015b60405180910390f35b3480156103f357600080fd5b5060025461040790610100900461ffff1681565b60405161ffff90911681526020016103de565b34801561042657600080fd5b5061043a610435366004614718565b610e36565b005b34801561044857600080fd5b50610451610f35565b6040516103de91906147b2565b34801561046a57600080fd5b506104c06104793660046147c5565b60009081527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559e602052604090205473ffffffffffffffffffffffffffffffffffffffff1690565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016103de565b3480156104f157600080fd5b5061043a610500366004614800565b610fe6565b34801561051157600080fd5b50610407601481565b34801561052657600080fd5b5061053a6105353660046147c5565b611148565b6040519081526020016103de565b34801561055457600080fd5b50600254610100900461ffff1661053a565b34801561057257600080fd5b5061043a610581366004614878565b61116e565b34801561059257600080fd5b5061053a6105a13660046148cd565b61125e565b3480156105b257600080fd5b5061043a6105c13660046148ea565b6112b0565b3480156105d257600080fd5b506002546105ec9065010000000000900463ffffffff1681565b60405163ffffffff90911681526020016103de565b34801561060d57600080fd5b5061053a611655565b34801561062257600080fd5b5061043a610631366004614878565b6116d9565b34801561064257600080fd5b5061053a604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527f421979463954f2ac93264f1ce0c11a780b4a7686122abe11bac8c8244f44a3de918101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260009060c00160405160208183030381529060405280519060200120905090565b3480156106f757600080fd5b5061043a6117af565b34801561070c57600080fd5b5061043a61071b3660046148ea565b6118bd565b34801561072c57600080fd5b5061043a61073b36600461492b565b6118d8565b34801561074c57600080fd5b5061043a61075b3660046149d8565b6119a2565b34801561076c57600080fd5b506103d261077b3660046147c5565b60009081527f3849b0d9a476107bbeb9ff6ae9ec519d63a65bac06efa495b84a43dbacfd9485602052604090205460ff1690565b3480156107bb57600080fd5b506104c06107ca3660046147c5565b611a35565b3480156107db57600080fd5b5061043a6107ea366004614a1a565b611a43565b61043a6107fd366004614a94565b611b31565b34801561080e57600080fd5b5061043a61081d3660046148cd565b611d12565b34801561082e57600080fd5b5061053a61083d3660046147c5565b611dee565b34801561084e57600080fd5b5061086261085d3660046148cd565b611e09565b6040516103de9190614aff565b34801561087b57600080fd5b5061053a61088a3660046148cd565b611e4d565b34801561089b57600080fd5b5061043a6108aa3660046147c5565b611e9c565b3480156108bb57600080fd5b5061053a6108ca3660046148cd565b73ffffffffffffffffffffffffffffffffffffffff1660009081527f24034dbc71162a0a127c76a8ce123f10641be888cbac564cd2e6e6f5e2c19b81602052604090205490565b34801561091d57600080fd5b5061043a611f48565b34801561093257600080fd5b506104c06109413660046147c5565b611ff8565b34801561095257600080fd5b506104c0612009565b34801561096757600080fd5b50610451612049565b34801561097c57600080fd5b5061053a6201518081565b34801561099357600080fd5b506104c061207a565b3480156109a857600080fd5b506108626109b73660046148cd565b6120a2565b3480156109c857600080fd5b5061053a6109d73660046148cd565b6120e6565b3480156109e857600080fd5b5061043a6109f73660046148cd565b612138565b348015610a0857600080fd5b5061043a610a17366004614b12565b6122e9565b348015610a2857600080fd5b5061053a610a373660046148cd565b61239f565b348015610a4857600080fd5b506002546103d29060ff1681565b348015610a6257600080fd5b5060025466038d7ea4c68000690100000000000000000090910460ff160261053a565b348015610a9157600080fd5b5061043a610aa03660046148cd565b6123f8565b348015610ab157600080fd5b5061053a610ac03660046147c5565b612466565b348015610ad157600080fd5b5061043a610ae0366004614b6f565b61247a565b348015610af157600080fd5b5061043a610b003660046149d8565b6125a5565b348015610b1157600080fd5b506104c07f000000000000000000000000000000000000000000000000000000000000000081565b348015610b4557600080fd5b5061043a610b543660046147c5565b612638565b348015610b6557600080fd5b50610451610b743660046147c5565b6126e4565b348015610b8557600080fd5b5061043a610b94366004614c6d565b6127be565b348015610ba557600080fd5b5061043a610bb4366004614c93565b612884565b348015610bc557600080fd5b50600254610407906301000000900461ffff1681565b348015610be757600080fd5b5061053a600581565b348015610bfc57600080fd5b506104c07f000000000000000000000000000000000000000000000000000000000000000081565b61043a610c32366004614cf2565b612a0e565b348015610c4357600080fd5b506103d2610c52366004614d5c565b73ffffffffffffffffffffffffffffffffffffffff91821660009081527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559f6020908152604080832093909416825291909152205460ff1690565b348015610cb857600080fd5b50610862610cc73660046148cd565b612c21565b348015610cd857600080fd5b5061043a610ce73660046148cd565b612c65565b348015610cf857600080fd5b5060025466038d7ea4c680006a010000000000000000000090910460ff160261053a565b348015610d2857600080fd5b5061043a612d8b565b348015610d3d57600080fd5b5061043a610d4c3660046149d8565b612e3e565b60007f01ffc9a7000000000000000000000000000000000000000000000000000000007fffffffff0000000000000000000000000000000000000000000000000000000083161480610de457507f80ac58cd000000000000000000000000000000000000000000000000000000007fffffffff000000000000000000000000000000000000000000000000000000008316145b80610e3057507f5b5e139f000000000000000000000000000000000000000000000000000000007fffffffff000000000000000000000000000000000000000000000000000000008316145b92915050565b7f87917b04fc43108fc3d291ac961b425fe1ddcf80087b2cb7e3c48f3e9233ea335473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610ebc576040517f5cd8319200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60025460ff1615610ef9576040517fde7c738300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002805461ffff9092166301000000027fffffffffffffffffffffffffffffffffffffffffffffffffffffff0000ffffff909216919091179055565b60607facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a955998054610f6390614d8a565b80601f0160208091040260200160405190810160405280929190818152602001828054610f8f90614d8a565b8015610fdc5780601f10610fb157610100808354040283529160200191610fdc565b820191906000526020600020905b815481529060010190602001808311610fbf57829003601f168201915b5050505050905090565b6000610ff9610ff483612ed1565b612f7a565b90503373ffffffffffffffffffffffffffffffffffffffff821614801590611071575073ffffffffffffffffffffffffffffffffffffffff811660009081527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559f6020908152604080832033845290915290205460ff16155b156110a8576040517f4fb505aa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008281527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559e602052604080822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff87811691821790925591518593918516917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a4505050565b60066020908152600782901c60009081529081205460fe600184901b161c600316610e30565b60148111156111a9576040517f4f2a111200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80158015906111dc57507f000000000000000000000000000000000000000000000000000000000000000062093a800142105b80156111ff5750600254611c2063ffffffff650100000000009092048216011642105b1561124e5760405173ffffffffffffffffffffffffffffffffffffffff841681527f7bf893b96a69dce258515c3db09a3b05e60d03e920a4ccc4c36bd5698772b35a9060200160405180910390a15b611259838383612f9c565b505050565b73ffffffffffffffffffffffffffffffffffffffff811660009081527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559c602052604081205460141c620fffff16610e30565b3073ffffffffffffffffffffffffffffffffffffffff8316036112ff576040517fdf5507ed00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff821661134c576040517fea553b3400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061135782612ed1565b905060003373ffffffffffffffffffffffffffffffffffffffff861614806113ce575073ffffffffffffffffffffffffffffffffffffffff851660009081527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559f6020908152604080832033845290915290205460ff165b8061141b575060008381527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559e602052604090205473ffffffffffffffffffffffffffffffffffffffff1633145b905080611454576040517f4fb505aa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8473ffffffffffffffffffffffffffffffffffffffff1661147483612f7a565b73ffffffffffffffffffffffffffffffffffffffff16146114c1576040517fa114810000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559e6020526040902080547fffffffffffffffffffffffff00000000000000000000000000000000000000001690556115216001840183612fdd565b61157a7fffffffffffffffffffffffff0000000000000000000000000000000000000000831673ffffffffffffffffffffffffffffffffffffffff8616175b740400000000000000000000000000000000000000001790565b60008481527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559d602090815260408083209390935573ffffffffffffffffffffffffffffffffffffffff8781168084527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559c90925283832080546001019055881680835283832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190559251869391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a45050505050565b60008060008061166361306a565b9050600060015b828110156116cf5760008181527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559d6020526040902054945084156116ac578491505b6116bb8260a11c600116151590565b156116c7578360010193505b60010161166a565b5091949350505050565b6014811115611714576040517f4f2a111200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6117417f000000000000000000000000000000000000000000000000000000000000000062093a80614e0c565b4210801561176d575060025461176a90620151809065010000000000900463ffffffff16614e0c565b42105b156117a4576040517f7d857b6700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611259838383613084565b7f87917b04fc43108fc3d291ac961b425fe1ddcf80087b2cb7e3c48f3e9233ea335473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611835576040517f5cd8319200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040514790600090339083908381818185875af1925050503d8060008114611879576040519150601f19603f3d011682016040523d82523d6000602084013e61187e565b606091505b50509050806118b9576040517f90b8ec1800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5050565b6112598383836040518060200160405280600081525061247a565b6118e886866001878787876130c6565b73ffffffffffffffffffffffffffffffffffffffff86811660008181527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559f60209081526040808320948a168084529482529182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001908117909155825190815291517f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c319281900390910190a3505050505050565b7f87917b04fc43108fc3d291ac961b425fe1ddcf80087b2cb7e3c48f3e9233ea335473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611a28576040517f5cd8319200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6003611259828483614e6a565b6000610e30610ff483612ed1565b7f87917b04fc43108fc3d291ac961b425fe1ddcf80087b2cb7e3c48f3e9233ea335473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611ac9576040517f5cd8319200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b83811015611b2a57611b1a858583818110611ae957611ae9614f84565b90506020020135848484818110611b0257611b02614f84565b9050602002013560066133f19092919063ffffffff16565b611b2381614fb3565b9050611acc565b5050505050565b323314611b6a576040517f9453980400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600254829061ffff6301000000820481166101009092041682011115611bbc576040517f4f2a111200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8260146005821115611bfa576040517f4f2a111200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80611c043361125e565b83011115611c3e576040517f4f2a111200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84611c6260025460ff69010000000000000000009091041666038d7ea4c680000290565b023414611c9b576040517fd2ade55600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600254611c2063ffffffff6501000000000090920482160116421080611cd0575060025465010000000000900463ffffffff16155b15611d07576040517fc7d08f0400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611b2a338686613401565b7f87917b04fc43108fc3d291ac961b425fe1ddcf80087b2cb7e3c48f3e9233ea335473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611d98576040517f5cd8319200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002805473ffffffffffffffffffffffffffffffffffffffff9092166b010000000000000000000000027fff0000000000000000000000000000000000000000ffffffffffffffffffffff909216919091179055565b6000610e30611dfc83612ed1565b60a81c64ffffffffff1690565b6060610e307facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a95599600401836001611e4860025461ffff6101009091041690565b6134fd565b73ffffffffffffffffffffffffffffffffffffffff811660009081527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559c6020526040812054620fffff16610e30565b7f87917b04fc43108fc3d291ac961b425fe1ddcf80087b2cb7e3c48f3e9233ea335473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611f22576040517f5cd8319200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611f2b816135c6565b6002600a6101000a81548160ff021916908360ff16021790555050565b7f87917b04fc43108fc3d291ac961b425fe1ddcf80087b2cb7e3c48f3e9233ea335473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611fce576040517f5cd8319200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600280547fffffffffffffffffffffffffffffffffffffffffffffff00000000ffffffffff169055565b6000610e3061200683612ed1565b90565b60007f87917b04fc43108fc3d291ac961b425fe1ddcf80087b2cb7e3c48f3e9233ea335b5473ffffffffffffffffffffffffffffffffffffffff16919050565b60607facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a955996001018054610f6390614d8a565b60007f3849b0d9a476107bbeb9ff6ae9ec519d63a65bac06efa495b84a43dbacfd948461202d565b6060610e307facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a955996004018360016120e160025461ffff6101009091041690565b613650565b73ffffffffffffffffffffffffffffffffffffffff811660009081527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559c602052604081205460281c620fffff16610e30565b7f87917b04fc43108fc3d291ac961b425fe1ddcf80087b2cb7e3c48f3e9233ea335473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146121be576040517f5cd8319200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8316906370a0823190602401602060405180830381865afa15801561222b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061224f9190614feb565b6040517fa9059cbb0000000000000000000000000000000000000000000000000000000081523360048201526024810182905290915073ffffffffffffffffffffffffffffffffffffffff83169063a9059cbb906044016020604051808303816000875af11580156122c5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112599190615004565b3360008181527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559f6020908152604080832073ffffffffffffffffffffffffffffffffffffffff87168085529083529281902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001686151590811790915590519081529192917f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a35050565b73ffffffffffffffffffffffffffffffffffffffff811660009081527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559c6020526040812054603c1c64ffffffffff16610e30565b905090565b6124006136de565b7f3849b0d9a476107bbeb9ff6ae9ec519d63a65bac06efa495b84a43dbacfd948480547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b6000610e3061247483612ed1565b60d01c90565b6124858484846112b0565b73ffffffffffffffffffffffffffffffffffffffff83163b1580159061256857506040517f150b7a02000000000000000000000000000000000000000000000000000000008082529073ffffffffffffffffffffffffffffffffffffffff85169063150b7a0290612500903390899088908890600401615021565b6020604051808303816000875af115801561251f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612543919061506a565b7fffffffff000000000000000000000000000000000000000000000000000000001614155b1561259f576040517ff5cd1f5d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50505050565b7f87917b04fc43108fc3d291ac961b425fe1ddcf80087b2cb7e3c48f3e9233ea335473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461262b576040517f5cd8319200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6004611259828483614e6a565b7f87917b04fc43108fc3d291ac961b425fe1ddcf80087b2cb7e3c48f3e9233ea335473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146126be576040517f5cd8319200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6126c7816135c6565b600260096101000a81548160ff021916908360ff16021790555050565b6060600380546126f390614d8a565b15905061272d57600361270583613766565b600460405160200161271993929190615118565b604051602081830303815290604052610e30565b6005805461273a90614d8a565b80601f016020809104026020016040519081016040528092919081815260200182805461276690614d8a565b80156127b35780601f10612788576101008083540402835291602001916127b3565b820191906000526020600020905b81548152906001019060200180831161279657829003601f168201915b505050505092915050565b7f87917b04fc43108fc3d291ac961b425fe1ddcf80087b2cb7e3c48f3e9233ea335473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614612844576040517f5cd8319200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002805463ffffffff90921665010000000000027fffffffffffffffffffffffffffffffffffffffffffffff00000000ffffffffff909216919091179055565b7f87917b04fc43108fc3d291ac961b425fe1ddcf80087b2cb7e3c48f3e9233ea335473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461290a576040517f5cd8319200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612914838361514b565b60025461ffff6301000000820481166101009092041682011115612964576040517f4f2a111200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b81156129bb5760005b848110156129b5576129a586868381811061298a5761298a614f84565b905060200201602081019061299f91906148cd565b856137c8565b6129ae81614fb3565b905061296d565b50611b2a565b60005b84811015612a06576129f68686838181106129db576129db614f84565b90506020020160208101906129f091906148cd565b856137d4565b6129ff81614fb3565b90506129be565b505050505050565b323314612a47576040517f9453980400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600254859061ffff6301000000820481166101009092041682011115612a99576040517f4f2a111200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b85846005821115612ad6576040517f4f2a111200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80612ae03361125e565b83011115612b1a576040517f4f2a111200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612b258585886137e1565b612b5b576040517f8baa579f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600254426501000000000090910463ffffffff908116611c2001161015612bae576040517f04cc9ce200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b87612bd360025460ff6a01000000000000000000009091041666038d7ea4c680000290565b023414612c0c576040517fd2ade55600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612c17338989613401565b5050505050505050565b6060610e307facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a95599600401836001612c6060025461ffff6101009091041690565b6138b3565b7f87917b04fc43108fc3d291ac961b425fe1ddcf80087b2cb7e3c48f3e9233ea335473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614612ceb576040517f5cd8319200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f87917b04fc43108fc3d291ac961b425fe1ddcf80087b2cb7e3c48f3e9233ea3380547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff831690811790915560408051338152602081019290925280517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c9281900390910190a150565b7f87917b04fc43108fc3d291ac961b425fe1ddcf80087b2cb7e3c48f3e9233ea335473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614612e11576040517f5cd8319200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055565b7f87917b04fc43108fc3d291ac961b425fe1ddcf80087b2cb7e3c48f3e9233ea335473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614612ec4576040517f5cd8319200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6005611259828483614e6a565b6000612edc8261392a565b612f12576040517fb1d04f0800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000825b60008181527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559d602052604090205491508115612f53575092915050565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01612f16565b6000612f8b8260a11c600116151590565b612f955781610e30565b3092915050565b60005b81811015612fd157612fc984848484818110612fbd57612fbd614f84565b90506020020135613948565b600101612f9f565b50611259838383613cad565b612fec8160a21c600116151590565b158015613024575060008281527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559d6020526040902054155b801561303457506130348261392a565b156118b95760009182527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559d602052604090912055565b600254600090610100900461ffff166123f3906001614e0c565b60005b818110156130b9576130b1848484848181106130a5576130a5614f84565b90506020020135613d68565b600101613087565b5061125960008383613cad565b42841015613100576040517f1ab7da6b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff871660009081527f24034dbc71162a0a127c76a8ce123f10641be888cbac564cd2e6e6f5e2c19b81602052604081208054600180820190925591906131f8604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527f421979463954f2ac93264f1ce0c11a780b4a7686122abe11bac8c8244f44a3de918101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260009060c00160405160208183030381529060405280519060200120905090565b604080517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9602082015273ffffffffffffffffffffffffffffffffffffffff808e1692820192909252908b166060820152608081018a905260a0810185905260c0810189905260e001604051602081830303815290604052805190602001206040516020016132b99291907f190100000000000000000000000000000000000000000000000000000000000081526002810192909252602282015260420190565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181528282528051602091820120600084529083018083525260ff881690820152606081018690526080810185905260a0016020604051602081039080840390855afa158015613335573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff811615806133af57508873ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b156133e6576040517f815e1d6400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505050505050505050565b6020928352600091825291902055565b60028211156134705760405173ffffffffffffffffffffffffffffffffffffffff841681527f9065519f7be06a0e88a7d6493f56b85b3714d9efd3de44c904169be8d4589b649060200160405180910390a160025461041f61010090910461ffff161015613470578160010191505b8080156134945750600254611c2063ffffffff650100000000009092048216011642105b156134e35760405173ffffffffffffffffffffffffffffffffffffffff841681527f7bf893b96a69dce258515c3db09a3b05e60d03e920a4ccc4c36bd5698772b35a9060200160405180910390a15b80156134f35761125983836137c8565b61125983836137d4565b60405160208101600080848601865b8181101561358d57600081815260208b905260409020549350831561352f578392505b8273ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff1614801561357657506135748360a11c600116151590565b155b15613585578085526020850194505b60010161350c565b505050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08282030160051c8252604052949350505050565b60008066038d7ea4c6800083061561360a576040517f4823b65a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5066038d7ea4c68000820460ff811115610e30576040517f4823b65a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405160208101600080848601865b8181101561358d57600081815260208b9052604090205493508315613682578392505b8273ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff161480156136c757506136c78360a11c600116151590565b156136d6578085526020850194505b60010161365f565b7f87917b04fc43108fc3d291ac961b425fe1ddcf80087b2cb7e3c48f3e9233ea335473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614613764576040517f5cd8319200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b606060a06040510180604052602081039150506000815280825b600183039250600a81066030018353600a90048061378057508190037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909101908152919050565b6118b9828260006140cb565b6118b98282600080614169565b60408051306020820152339181019190915260608101829052600090819060800160405160208183030381529060405280519060200120905060006138578686613850856020527b19457468657265756d205369676e6564204d6573736167653a0a3332600052603c60042090565b919061449d565b905073ffffffffffffffffffffffffffffffffffffffff8116158015906138a7575060025473ffffffffffffffffffffffffffffffffffffffff8281166b01000000000000000000000090920416145b925050505b9392505050565b60405160208101600080848601865b8181101561358d57600081815260208b90526040902054935083156138e5578392505b8273ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff1603613922578085526020850194505b6001016138c2565b600081600111158015610e30575061394061306a565b821092915050565b600061395382612ed1565b905060003373ffffffffffffffffffffffffffffffffffffffff851614806139ca575073ffffffffffffffffffffffffffffffffffffffff841660009081527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559f6020908152604080832033845290915290205460ff165b80613a17575060008381527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559e602052604090205473ffffffffffffffffffffffffffffffffffffffff1633145b905080613a50576040517f4fb505aa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8373ffffffffffffffffffffffffffffffffffffffff16613a7083612f7a565b73ffffffffffffffffffffffffffffffffffffffff1614613abd576040517f3a6bbed300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559e6020526040902080547fffffffffffffffffffffffff0000000000000000000000000000000000000000169055613b1d6001840183612fdd565b613b867ffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffff4260a81b7fffffffffffff0000000000ffffffffffffffffffffffffffffffffffffffffff851617740200000000000000000000000000000000000000001716611560565b60008481527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559d602090815260408083209390935573ffffffffffffffffffffffffffffffffffffffff871682527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559c90522054613c3390429065010000000000015b7ffffffffffffffffffffffffffffffffffffffff0000000000fffffffffffffff16603c9190911b1790565b73ffffffffffffffffffffffffffffffffffffffff851660008181527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559c602052604080822093909355915185923092917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9190a450505050565b6112597f4d26d408b9c238c45bbfb91e45a4a735db7b67a74ca6c269254d8aa9b124208a848484604051602401613ce693929190615188565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009093169290921790915261450c565b6000613d7382612ed1565b905060003373ffffffffffffffffffffffffffffffffffffffff85161480613dea575073ffffffffffffffffffffffffffffffffffffffff841660009081527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559f6020908152604080832033845290915290205460ff165b80613e37575060008381527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559e602052604090205473ffffffffffffffffffffffffffffffffffffffff1633145b905080613e70576040517f4fb505aa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b613e7f8260a11c600116151590565b613eb5576040517f30c8d84f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff84168273ffffffffffffffffffffffffffffffffffffffff1614613f1a576040517f3a6bbed300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b613f298260a01c600116151590565b15613f6957613f3b8360010183612fdd565b613f667ffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffff8316611560565b91505b7fffffffffffff0000000000fdffffffffffffffffffffffffffffffffffffffff82167ffffffffffffffffffffffffdffffffffffffffffffffffffffffffffffffffff4260a81b161760008481527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559d602090815260408083209390935573ffffffffffffffffffffffffffffffffffffffff871682527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559c905220546140529042907fffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000001613c07565b73ffffffffffffffffffffffffffffffffffffffff851660008181527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559c6020526040808220939093559151859230917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9190a450505050565b60006140d561306a565b90506140e48484600185614169565b60008367ffffffffffffffff8111156140ff576140ff614b40565b604051908082528060200260200182016040528015614128578160200160208202803683370190505b50905060005b8481101561415e5780830182828151811061414b5761414b614f84565b602090810291909101015260010161412e565b50611b2a8582614657565b826000036141a3576040517fb562e8dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff84166141f0576040517f2e07630000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006141fa61306a565b73ffffffffffffffffffffffffffffffffffffffff861660008181527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559c602052604090205491925060d084901b7fffffffffffff0000000000000000000000000000000000000000000000000000161790600186900361428f5774040000000000000000000000000000000000000000821791505b84156143a657740200000000000000000000000000000000000000007fffffffffffff0000000000ffffffffffffffffffffffffffffffffffffffffff740100000000000000000000000000000000000000008417164260a81b171791506142fd42613c07838960281b0190565b905060005b868110156143a0576040518482019073ffffffffffffffffffffffffffffffffffffffff8a16906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908290a460405184820190309073ffffffffffffffffffffffffffffffffffffffff8b16907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90600090a4600101614302565b50614402565b60005b86811015614400576040518482019073ffffffffffffffffffffffffffffffffffffffff8a16906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908290a46001016143a9565b505b61441686614412838260141b0190565b0190565b73ffffffffffffffffffffffffffffffffffffffff881660009081527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559c60209081526040808320939093558582527facef0a52ec0e8b948b85810f48a276692a03896348e0958ead290f1909a9559d9052208290556144948661468e565b50505050505050565b6000604182036138ac576040516040846040377f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0606051116145025784600052604084013560001a602052602060406080600060015afa5060006060523d6060035191505b6040529392505050565b7f3849b0d9a476107bbeb9ff6ae9ec519d63a65bac06efa495b84a43dbacfd94845473ffffffffffffffffffffffffffffffffffffffff1661457a576040517fafd0683400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001663b47204777f3849b0d9a476107bbeb9ff6ae9ec519d63a65bac06efa495b84a43dbacfd9484546040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b1681526146299173ffffffffffffffffffffffffffffffffffffffff169085906004016151fa565b600060405180830381600087803b15801561464357600080fd5b505af1158015611b2a573d6000803e3d6000fd5b6118b97f4d26d408b9c238c45bbfb91e45a4a735db7b67a74ca6c269254d8aa9b124208a8383604051602401613ce6929190615231565b80600260018282829054906101000a900461ffff166146ad9190615260565b92506101000a81548161ffff021916908361ffff16021790555050565b7fffffffff00000000000000000000000000000000000000000000000000000000811681146146f857600080fd5b50565b60006020828403121561470d57600080fd5b81356138ac816146ca565b60006020828403121561472a57600080fd5b813561ffff811681146138ac57600080fd5b60005b8381101561475757818101518382015260200161473f565b8381111561259f5750506000910152565b6000815180845261478081602086016020860161473c565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006138ac6020830184614768565b6000602082840312156147d757600080fd5b5035919050565b73ffffffffffffffffffffffffffffffffffffffff811681146146f857600080fd5b6000806040838503121561481357600080fd5b823561481e816147de565b946020939093013593505050565b60008083601f84011261483e57600080fd5b50813567ffffffffffffffff81111561485657600080fd5b6020830191508360208260051b850101111561487157600080fd5b9250929050565b60008060006040848603121561488d57600080fd5b8335614898816147de565b9250602084013567ffffffffffffffff8111156148b457600080fd5b6148c08682870161482c565b9497909650939450505050565b6000602082840312156148df57600080fd5b81356138ac816147de565b6000806000606084860312156148ff57600080fd5b833561490a816147de565b9250602084013561491a816147de565b929592945050506040919091013590565b60008060008060008060c0878903121561494457600080fd5b863561494f816147de565b9550602087013561495f816147de565b945060408701359350606087013560ff8116811461497c57600080fd5b9598949750929560808101359460a0909101359350915050565b60008083601f8401126149a857600080fd5b50813567ffffffffffffffff8111156149c057600080fd5b60208301915083602082850101111561487157600080fd5b600080602083850312156149eb57600080fd5b823567ffffffffffffffff811115614a0257600080fd5b614a0e85828601614996565b90969095509350505050565b60008060008060408587031215614a3057600080fd5b843567ffffffffffffffff80821115614a4857600080fd5b614a548883890161482c565b90965094506020870135915080821115614a6d57600080fd5b50614a7a8782880161482c565b95989497509550505050565b80151581146146f857600080fd5b60008060408385031215614aa757600080fd5b823591506020830135614ab981614a86565b809150509250929050565b600081518084526020808501945080840160005b83811015614af457815187529582019590820190600101614ad8565b509495945050505050565b6020815260006138ac6020830184614ac4565b60008060408385031215614b2557600080fd5b8235614b30816147de565b91506020830135614ab981614a86565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60008060008060808587031215614b8557600080fd5b8435614b90816147de565b93506020850135614ba0816147de565b925060408501359150606085013567ffffffffffffffff80821115614bc457600080fd5b818701915087601f830112614bd857600080fd5b813581811115614bea57614bea614b40565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f01168101908382118183101715614c3057614c30614b40565b816040528281528a6020848701011115614c4957600080fd5b82602086016020830137600060208483010152809550505050505092959194509250565b600060208284031215614c7f57600080fd5b813563ffffffff811681146138ac57600080fd5b60008060008060608587031215614ca957600080fd5b843567ffffffffffffffff811115614cc057600080fd5b614ccc8782880161482c565b909550935050602085013591506040850135614ce781614a86565b939692955090935050565b600080600080600060808688031215614d0a57600080fd5b853594506020860135614d1c81614a86565b935060408601359250606086013567ffffffffffffffff811115614d3f57600080fd5b614d4b88828901614996565b969995985093965092949392505050565b60008060408385031215614d6f57600080fd5b8235614d7a816147de565b91506020830135614ab9816147de565b600181811c90821680614d9e57607f821691505b602082108103614dd7577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008219821115614e1f57614e1f614ddd565b500190565b601f82111561125957600081815260208120601f850160051c81016020861015614e4b5750805b601f850160051c820191505b81811015612a0657828155600101614e57565b67ffffffffffffffff831115614e8257614e82614b40565b614e9683614e908354614d8a565b83614e24565b6000601f841160018114614ee85760008515614eb25750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b1c1916600186901b178355611b2a565b6000838152602090207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0861690835b82811015614f375786850135825560209485019460019092019101614f17565b5086821015614f72577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88860031b161c19848701351681555b505060018560011b0183555050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203614fe457614fe4614ddd565b5060010190565b600060208284031215614ffd57600080fd5b5051919050565b60006020828403121561501657600080fd5b81516138ac81614a86565b600073ffffffffffffffffffffffffffffffffffffffff8087168352808616602084015250836040830152608060608301526150606080830184614768565b9695505050505050565b60006020828403121561507c57600080fd5b81516138ac816146ca565b6000815461509481614d8a565b600182811680156150ac57600181146150df5761510e565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008416875282151583028701945061510e565b8560005260208060002060005b858110156151055781548a8201529084019082016150ec565b50505082870194505b5050505092915050565b60006151248286615087565b845161513481836020890161473c565b61514081830186615087565b979650505050505050565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048311821515161561518357615183614ddd565b500290565b73ffffffffffffffffffffffffffffffffffffffff841681526040602082015281604082015260007f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8311156151dd57600080fd5b8260051b8085606085013760009201606001918252509392505050565b73ffffffffffffffffffffffffffffffffffffffff831681526040602082015260006152296040830184614768565b949350505050565b73ffffffffffffffffffffffffffffffffffffffff831681526040602082015260006152296040830184614ac4565b600061ffff80831681851680830382111561527d5761527d614ddd565b0194935050505056fea164736f6c634300080f000a697066733a2f2f516d547639566f58676b5a7846636f6d5457336b4e364352727955504d666765556b56656b46737a63643739674b2f00000000000000000000000086e4dc95c7fbdbf52e33d563bbdb00823894c287000000000000000000000000fe5e5d361b2ad62c541bab87c45a0b9b018389a2

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

00000000000000000000000086e4dc95c7fbdbf52e33d563bbdb00823894c287000000000000000000000000fe5e5d361b2ad62c541bab87c45a0b9b018389a2

-----Decoded View---------------
Arg [0] : checkpointManager (address): 0x86E4Dc95c7FBdBf52e33D563BbDB00823894C287
Arg [1] : fxRoot (address): 0xfe5e5D361b2ad62c541bAb87C45a0B9B018389a2

-----Encoded View---------------
2 Constructor Arguments found :
Arg [0] : 00000000000000000000000086e4dc95c7fbdbf52e33d563bbdb00823894c287
Arg [1] : 000000000000000000000000fe5e5d361b2ad62c541bab87c45a0b9b018389a2


Block Transaction Difficulty Gas Used Reward
Block Uncle Number Difficulty Gas Used Reward
Loading
Loading
Make sure to use the "Vote Down" button for any spammy posts, and the "Vote Up" for interesting conversations.

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.