ETH Price: $2,939.54 (-0.47%)

Contract

0x7e9B9b72f9aaf4e2aA4EB29411245742496C37fD

Overview

ETH Balance

0 ETH

ETH Value

$0.00

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Block
From
To

There are no matching entries

1 Internal Transaction found.

Latest 1 internal transaction

Advanced mode:
Parent Transaction Hash Block From To
298124472025-12-10 7:04:1145 days ago1765350251  Contract Creation0 ETH
Cross-Chain Transactions
Loading...
Loading

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

Contract Name:
ERC20ClaimWindowMerkleClaimzk

Compiler Version
v0.8.28+commit.7893614a

ZkSolc Version
v1.5.15

Optimization Enabled:
Yes with Mode 3

Other Settings:
paris EvmVersion

Contract Source Code (Solidity Standard Json-Input format)

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

import {IERC20SafeTransfers} from "@animoca/ethereum-contracts/contracts/token/ERC20/interfaces/IERC20SafeTransfers.sol";
import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import {ContractOwnership} from "@animoca/ethereum-contracts/contracts/access/ContractOwnership.sol";
import {ContractOwnershipStorage} from "@animoca/ethereum-contracts/contracts/access/libraries/ContractOwnershipStorage.sol";

contract ERC20ClaimWindowMerkleClaimzk is ContractOwnership {
    using ContractOwnershipStorage for ContractOwnershipStorage.Layout;
    using MerkleProof for bytes32[];

    /// @notice The status of the claim
    enum ClaimError {
        NoError, // 0
        EpochIdNotExists, // 1
        OutOfClaimWindow, // 2
        AlreadyClaimed // 3
    }

    /// @notice The claim window struct.
    struct ClaimWindow {
        bytes32 merkleRoot;
        uint256 startTime;
        uint256 endTime;
    }

    /// @notice a reference to the reward token contract
    IERC20SafeTransfers public immutable REWARD_TOKEN;

    /// @notice a reference to the staking pool contract
    address public immutable STAKING_POOL;

    /// @notice The address of the token holder wallet.
    address public tokenHolderWallet;

    /// @notice Mapping of the epoch ID to the claim window.
    mapping(bytes32 epochId => ClaimWindow claimWindow) public claimWindows;

    /// @notice Mapping of leaf hash to claimed state
    mapping(bytes32 leaf => bool claimed) public claimed;

    /// @notice Event emitted when the token holder wallet is set.
    /// @param newTokenHolderWallet The address of the new token holder wallet.
    event TokenHolderWalletSet(address indexed newTokenHolderWallet);

    /// @notice Event emitted when an epoch id is set with its merkle root and claim window.
    /// @param epochId The unique epoch ID.
    /// @param merkleRoot The merkle root in the claim window.
    /// @param startTime The start time of the claim window.
    /// @param endTime The end time of the claim window.
    event EpochMerkleRootSet(bytes32 indexed epochId, bytes32 indexed merkleRoot, uint256 startTime, uint256 indexed endTime);

    /// @notice Emitted when a payout is claimed.
    /// @param epochId The epoch ID being claimed.
    /// @param root The merkle root of the claim.
    /// @param recipient The recipient of the claim.
    /// @param amount The amount of token claimed.
    event PayoutClaimed(bytes32 indexed epochId, bytes32 indexed root, address indexed recipient, uint256 amount);

    /// @notice Thrown when the reward token contract address is zero.
    error InvalidRewardToken();

    /// @notice Thrown when the staking pool address is zero.
    error InvalidStakingPool();

    /// @notice Thrown when the merkle root is zero.
    error InvalidMerkleRoot();

    /// @notice Thrown when the claim window is invalid.
    error InvalidClaimWindow(uint256 startTime, uint256 endTime, uint256 currentTime);

    /// @notice Thrown when the epoch ID already exists.
    error EpochIdAlreadyExists(bytes32 epochId);

    /// @notice Thrown when the proof provided for the claim is invalid.
    error InvalidProof(bytes32 epochId, address recipient, uint256 amount);

    /// @notice Thrown when the epoch ID does not exist.
    error EpochIdNotExists(bytes32 epochId);

    /// @notice Thrown when the claim happens on outside the claim window
    error OutOfClaimWindow(bytes32 epochId, uint256 currentTime);

    /// @notice Thrown when the leaf is claimed already.
    error AlreadyClaimed(bytes32 epochId, bytes32 leaf);

    constructor(address rewardToken_, address stakingPool_, address tokenHolderWallet_) ContractOwnership(msg.sender) {
        if (rewardToken_ == address(0)) {
            revert InvalidRewardToken();
        }
        REWARD_TOKEN = IERC20SafeTransfers(rewardToken_);

        if (stakingPool_ == address(0)) {
            revert InvalidStakingPool();
        }
        STAKING_POOL = stakingPool_;

        tokenHolderWallet = tokenHolderWallet_;
        emit TokenHolderWalletSet(tokenHolderWallet_);
    }

    /**
     * @notice Sets the merkle root for a specific epoch with start and end time.
     * @dev Reverts if _msgSender() is not the owner.
     * @dev Reverts if the merkle root is zero.
     * @dev Reverts if the claim window is invalid.
     * @dev Reverts if the epoch ID is set.
     * @dev Emits a {EpochMerkleRootSet} event.
     * @param epochId The epoch ID assigned for the claim window.
     * @param merkleRoot The Merkle root of the claim window.
     * @param startTime The start time of the claim window.
     * @param endTime The end time of the claim window.
     */
    function setEpochMerkleRoot(bytes32 epochId, bytes32 merkleRoot, uint256 startTime, uint256 endTime) external {
        ContractOwnershipStorage.layout().enforceIsContractOwner(_msgSender());

        if (merkleRoot == bytes32(0)) {
            revert InvalidMerkleRoot();
        }

        if (startTime >= endTime || endTime <= block.timestamp) {
            revert InvalidClaimWindow(startTime, endTime, block.timestamp);
        }

        if (claimWindows[epochId].merkleRoot != bytes32(0)) {
            revert EpochIdAlreadyExists(epochId);
        }

        claimWindows[epochId] = ClaimWindow(merkleRoot, startTime, endTime);

        emit EpochMerkleRootSet(epochId, merkleRoot, startTime, endTime);
    }

    /**
     * @notice Sets the token holder wallet address.
     * @dev Reverts if _msgSender() is not the owner.
     * @dev Emits a {TokenHolderWalletSet} event.
     * @param newTokenHolderWallet The address of the new token holder wallet.
     */
    function setTokenHolderWallet(address newTokenHolderWallet) external {
        ContractOwnershipStorage.layout().enforceIsContractOwner(_msgSender());
        tokenHolderWallet = newTokenHolderWallet;
        emit TokenHolderWalletSet(newTokenHolderWallet);
    }

    /**
     * @notice Claim and stake the payout with a given epoch ID
     * @dev Reverts with {EpochIdNotExists} if epoch id does not exist.
     * @dev Reverts with {OutOfClaimWindow} if current block time is outside of the claim window.
     * @dev Reverts with {AlreadyClaimed} if the specified payout has already been claimed.
     * @dev Reverts with {InvalidProof} if the merkle leaf/proof is invalid.
     * @dev Emits a {PayoutClaimed} event.
     * @param epochId The unique epoch ID associated with the claim window.
     * @param recipient The recipient of the claim.
     * @param amount The amount of the token to be claimed.
     * @param proof The Merkle proof for the claim.
     */
    function claimAndStake(bytes32 epochId, address recipient, uint256 amount, bytes32[] calldata proof) external {
        bytes32 leaf = keccak256(abi.encodePacked(epochId, recipient, amount));
        ClaimWindow storage claimWindow = claimWindows[epochId];

        ClaimError canClaimResult = _canClaim(claimWindow, leaf);
        if (canClaimResult == ClaimError.EpochIdNotExists) {
            revert EpochIdNotExists(epochId);
        } else if (canClaimResult == ClaimError.OutOfClaimWindow) {
            revert OutOfClaimWindow(epochId, block.timestamp);
        } else if (canClaimResult == ClaimError.AlreadyClaimed) {
            revert AlreadyClaimed(epochId, leaf);
        }

        bytes32 root = claimWindow.merkleRoot;
        if (!proof.verifyCalldata(root, leaf)) {
            revert InvalidProof(epochId, recipient, amount);
        }

        claimed[leaf] = true;

        REWARD_TOKEN.safeTransferFrom(tokenHolderWallet, STAKING_POOL, amount, abi.encode(recipient));
        emit PayoutClaimed(epochId, root, recipient, amount);
    }

    /**
     * @notice Checks if a recipient can claim a reward for a given epoch id
     * @param epochId The unique epoch ID associated with the claim window.
     * @param recipient The recipient of the claim.
     * @param amount The amount of the token to be claimed.
     */
    function canClaim(bytes32 epochId, address recipient, uint256 amount) external view returns (ClaimError) {
        return _canClaim(claimWindows[epochId], keccak256(abi.encodePacked(epochId, recipient, amount)));
    }

    /**
     * @notice
     * 1) Returns ClaimError.EpochIdNotExists if merkle root of the claim window has not been set,
     * 2) Returns ClaimError.OutOfClaimWindow if current time is beyond start time and end time of the claim window,
     * 3) Returns ClaimError.AlreadyClaimed if recipient has already claimed,
     * 4) Returns ClaimError.NoError
     * @param claimWindow The claim window of the claim.
     * @param leaf The leaf of the claim.
     */
    function _canClaim(ClaimWindow storage claimWindow, bytes32 leaf) internal view returns (ClaimError) {
        if (claimWindow.merkleRoot == bytes32(0)) {
            return ClaimError.EpochIdNotExists;
        }
        if (block.timestamp < claimWindow.startTime || block.timestamp > claimWindow.endTime) {
            return ClaimError.OutOfClaimWindow;
        }
        if (claimed[leaf]) {
            return ClaimError.AlreadyClaimed;
        }

        return ClaimError.NoError;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/MerkleProof.sol)
// This file was procedurally generated from scripts/generate/templates/MerkleProof.js.

pragma solidity ^0.8.20;

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

/**
 * @dev These functions deal with verification of Merkle Tree proofs.
 *
 * The tree and the proofs can be generated using our
 * https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
 * You will find a quickstart guide in the readme.
 *
 * WARNING: You should avoid using leaf values that are 64 bytes long prior to
 * hashing, or use a hash function other than keccak256 for hashing leaves.
 * This is because the concatenation of a sorted pair of internal nodes in
 * the Merkle tree could be reinterpreted as a leaf value.
 * OpenZeppelin's JavaScript library generates Merkle trees that are safe
 * against this attack out of the box.
 *
 * IMPORTANT: Consider memory side-effects when using custom hashing functions
 * that access memory in an unsafe way.
 *
 * NOTE: This library supports proof verification for merkle trees built using
 * custom _commutative_ hashing functions (i.e. `H(a, b) == H(b, a)`). Proving
 * leaf inclusion in trees built using non-commutative hashing functions requires
 * additional logic that is not supported by this library.
 */
library MerkleProof {
    /**
     *@dev The multiproof provided is not valid.
     */
    error MerkleProofInvalidMultiproof();

    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     *
     * This version handles proofs in memory with the default hashing function.
     */
    function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        return processProof(proof, leaf) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leaves & pre-images are assumed to be sorted.
     *
     * This version handles proofs in memory with the default hashing function.
     */
    function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     *
     * This version handles proofs in memory with a custom hashing function.
     */
    function verify(
        bytes32[] memory proof,
        bytes32 root,
        bytes32 leaf,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bool) {
        return processProof(proof, leaf, hasher) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leaves & pre-images are assumed to be sorted.
     *
     * This version handles proofs in memory with a custom hashing function.
     */
    function processProof(
        bytes32[] memory proof,
        bytes32 leaf,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = hasher(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     *
     * This version handles proofs in calldata with the default hashing function.
     */
    function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        return processProofCalldata(proof, leaf) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leaves & pre-images are assumed to be sorted.
     *
     * This version handles proofs in calldata with the default hashing function.
     */
    function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     *
     * This version handles proofs in calldata with a custom hashing function.
     */
    function verifyCalldata(
        bytes32[] calldata proof,
        bytes32 root,
        bytes32 leaf,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bool) {
        return processProofCalldata(proof, leaf, hasher) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leaves & pre-images are assumed to be sorted.
     *
     * This version handles proofs in calldata with a custom hashing function.
     */
    function processProofCalldata(
        bytes32[] calldata proof,
        bytes32 leaf,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = hasher(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
     * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
     *
     * This version handles multiproofs in memory with the default hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
     * The `leaves` must be validated independently. See {processMultiProof}.
     */
    function multiProofVerify(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProof(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
     * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
     * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
     * respectively.
     *
     * This version handles multiproofs in memory with the default hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
     * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
     * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
     *
     * NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
     * and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
     * validating the leaves elsewhere.
     */
    function processMultiProof(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32[] memory leaves
    ) internal pure returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the Merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofFlagsLen = proofFlags.length;

        // Check proof validity.
        if (leavesLen + proof.length != proofFlagsLen + 1) {
            revert MerkleProofInvalidMultiproof();
        }

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](proofFlagsLen);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < proofFlagsLen; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = Hashes.commutativeKeccak256(a, b);
        }

        if (proofFlagsLen > 0) {
            if (proofPos != proof.length) {
                revert MerkleProofInvalidMultiproof();
            }
            unchecked {
                return hashes[proofFlagsLen - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    /**
     * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
     * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
     *
     * This version handles multiproofs in memory with a custom hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
     * The `leaves` must be validated independently. See {processMultiProof}.
     */
    function multiProofVerify(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32 root,
        bytes32[] memory leaves,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bool) {
        return processMultiProof(proof, proofFlags, leaves, hasher) == root;
    }

    /**
     * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
     * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
     * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
     * respectively.
     *
     * This version handles multiproofs in memory with a custom hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
     * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
     * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
     *
     * NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
     * and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
     * validating the leaves elsewhere.
     */
    function processMultiProof(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32[] memory leaves,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the Merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofFlagsLen = proofFlags.length;

        // Check proof validity.
        if (leavesLen + proof.length != proofFlagsLen + 1) {
            revert MerkleProofInvalidMultiproof();
        }

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](proofFlagsLen);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < proofFlagsLen; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = hasher(a, b);
        }

        if (proofFlagsLen > 0) {
            if (proofPos != proof.length) {
                revert MerkleProofInvalidMultiproof();
            }
            unchecked {
                return hashes[proofFlagsLen - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    /**
     * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
     * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
     *
     * This version handles multiproofs in calldata with the default hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
     * The `leaves` must be validated independently. See {processMultiProofCalldata}.
     */
    function multiProofVerifyCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProofCalldata(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
     * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
     * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
     * respectively.
     *
     * This version handles multiproofs in calldata with the default hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
     * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
     * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
     *
     * NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
     * and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
     * validating the leaves elsewhere.
     */
    function processMultiProofCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32[] memory leaves
    ) internal pure returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the Merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofFlagsLen = proofFlags.length;

        // Check proof validity.
        if (leavesLen + proof.length != proofFlagsLen + 1) {
            revert MerkleProofInvalidMultiproof();
        }

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](proofFlagsLen);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < proofFlagsLen; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = Hashes.commutativeKeccak256(a, b);
        }

        if (proofFlagsLen > 0) {
            if (proofPos != proof.length) {
                revert MerkleProofInvalidMultiproof();
            }
            unchecked {
                return hashes[proofFlagsLen - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    /**
     * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
     * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
     *
     * This version handles multiproofs in calldata with a custom hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
     * The `leaves` must be validated independently. See {processMultiProofCalldata}.
     */
    function multiProofVerifyCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32 root,
        bytes32[] memory leaves,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bool) {
        return processMultiProofCalldata(proof, proofFlags, leaves, hasher) == root;
    }

    /**
     * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
     * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
     * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
     * respectively.
     *
     * This version handles multiproofs in calldata with a custom hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
     * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
     * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
     *
     * NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
     * and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
     * validating the leaves elsewhere.
     */
    function processMultiProofCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32[] memory leaves,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the Merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofFlagsLen = proofFlags.length;

        // Check proof validity.
        if (leavesLen + proof.length != proofFlagsLen + 1) {
            revert MerkleProofInvalidMultiproof();
        }

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](proofFlagsLen);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < proofFlagsLen; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = hasher(a, b);
        }

        if (proofFlagsLen > 0) {
            if (proofPos != proof.length) {
                revert MerkleProofInvalidMultiproof();
            }
            unchecked {
                return hashes[proofFlagsLen - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }
}

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

import {ContractOwnershipStorage} from "./libraries/ContractOwnershipStorage.sol";
import {ContractOwnershipBase} from "./base/ContractOwnershipBase.sol";
import {InterfaceDetection} from "./../introspection/InterfaceDetection.sol";

/// @title ERC173 Contract Ownership Standard (immutable version).
/// @dev See https://eips.ethereum.org/EIPS/eip-173
/// @dev This contract is to be used via inheritance in an immutable (non-proxied) implementation.
abstract contract ContractOwnership is ContractOwnershipBase, InterfaceDetection {
    using ContractOwnershipStorage for ContractOwnershipStorage.Layout;

    /// @notice Initializes the storage with an initial contract owner.
    /// @notice Marks the following ERC165 interface(s) as supported: ERC173.
    /// @dev Emits an {OwnershipTransferred} if `initialOwner` is not the zero address.
    /// @param initialOwner the initial contract owner.
    constructor(address initialOwner) {
        ContractOwnershipStorage.layout().constructorInit(initialOwner);
    }
}

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

import {NotContractOwner, NotTargetContractOwner} from "./../errors/ContractOwnershipErrors.sol";
import {TargetIsNotAContract} from "./../errors/Common.sol";
import {OwnershipTransferred} from "./../events/ERC173Events.sol";
import {IERC173} from "./../interfaces/IERC173.sol";
import {Address} from "./../../utils/libraries/Address.sol";
import {ProxyInitialization} from "./../../proxy/libraries/ProxyInitialization.sol";
import {InterfaceDetectionStorage} from "./../../introspection/libraries/InterfaceDetectionStorage.sol";

library ContractOwnershipStorage {
    using Address for address;
    using ContractOwnershipStorage for ContractOwnershipStorage.Layout;
    using InterfaceDetectionStorage for InterfaceDetectionStorage.Layout;

    struct Layout {
        address contractOwner;
    }

    bytes32 internal constant LAYOUT_STORAGE_SLOT = bytes32(uint256(keccak256("animoca.core.access.ContractOwnership.storage")) - 1);
    bytes32 internal constant PROXY_INIT_PHASE_SLOT = bytes32(uint256(keccak256("animoca.core.access.ContractOwnership.phase")) - 1);

    /// @notice Initializes the storage with an initial contract owner (immutable version).
    /// @notice Marks the following ERC165 interface(s) as supported: ERC173.
    /// @dev Note: This function should be called ONLY in the constructor of an immutable (non-proxied) contract.
    /// @dev Emits an {OwnershipTransferred} if `initialOwner` is not the zero address.
    /// @param initialOwner The initial contract owner.
    function constructorInit(Layout storage s, address initialOwner) internal {
        if (initialOwner != address(0)) {
            s.contractOwner = initialOwner;
            emit OwnershipTransferred(address(0), initialOwner);
        }
        InterfaceDetectionStorage.layout().setSupportedInterface(type(IERC173).interfaceId, true);
    }

    /// @notice Initializes the storage with an initial contract owner (proxied version).
    /// @notice Sets the proxy initialization phase to `1`.
    /// @notice Marks the following ERC165 interface(s) as supported: ERC173.
    /// @dev Note: This function should be called ONLY in the init function of a proxied contract.
    /// @dev Reverts with {InitializationPhaseAlreadyReached} if the proxy initialization phase is set to `1` or above.
    /// @dev Emits an {OwnershipTransferred} if `initialOwner` is not the zero address.
    /// @param initialOwner The initial contract owner.
    function proxyInit(Layout storage s, address initialOwner) internal {
        ProxyInitialization.setPhase(PROXY_INIT_PHASE_SLOT, 1);
        s.constructorInit(initialOwner);
    }

    /// @notice Sets the address of the new contract owner.
    /// @dev Reverts with {NotContractOwner} if `sender` is not the contract owner.
    /// @dev Emits an {OwnershipTransferred} event if `newOwner` is different from the current contract owner.
    /// @param newOwner The address of the new contract owner. Using the zero address means renouncing ownership.
    function transferOwnership(Layout storage s, address sender, address newOwner) internal {
        address previousOwner = s.contractOwner;
        if (sender != previousOwner) revert NotContractOwner(sender);
        if (previousOwner != newOwner) {
            s.contractOwner = newOwner;
            emit OwnershipTransferred(previousOwner, newOwner);
        }
    }

    /// @notice Gets the address of the contract owner.
    /// @return contractOwner The address of the contract owner.
    function owner(Layout storage s) internal view returns (address contractOwner) {
        return s.contractOwner;
    }

    /// @notice Checks whether an account is the owner of a target contract.
    /// @param targetContract The contract to check.
    /// @param account The account to check.
    /// @return isTargetContractOwner_ Whether `account` is the owner of `targetContract`.
    function isTargetContractOwner(address targetContract, address account) internal view returns (bool isTargetContractOwner_) {
        if (!targetContract.hasBytecode()) revert TargetIsNotAContract(targetContract);
        return IERC173(targetContract).owner() == account;
    }

    /// @notice Ensures that an account is the contract owner.
    /// @dev Reverts with {NotContractOwner} if `account` is not the contract owner.
    /// @param account The account.
    function enforceIsContractOwner(Layout storage s, address account) internal view {
        if (account != s.contractOwner) revert NotContractOwner(account);
    }

    /// @notice Enforces that an account is the owner of a target contract.
    /// @dev Reverts with {NotTheTargetContractOwner} if the account is not the owner.
    /// @param targetContract The contract to check.
    /// @param account The account to check.
    function enforceIsTargetContractOwner(address targetContract, address account) internal view {
        if (!isTargetContractOwner(targetContract, account)) revert NotTargetContractOwner(targetContract, account);
    }

    function layout() internal pure returns (Layout storage s) {
        bytes32 position = LAYOUT_STORAGE_SLOT;
        assembly {
            s.slot := position
        }
    }
}

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

/// @title ERC20 Token Standard, optional extension: Safe Transfers.
/// @dev See https://eips.ethereum.org/EIPS/eip-20
/// @dev Note: the ERC-165 identifier for this interface is 0x53f41a97.
interface IERC20SafeTransfers {
    /// @notice Transfers an amount of tokens to a recipient from the sender. If the recipient is a contract, calls `onERC20Received` on it.
    /// @dev Reverts if `to` is the zero address.
    /// @dev Reverts if the sender does not have at least `value` of balance.
    /// @dev Reverts if `to` is a contract and the call to `onERC20Received` fails, reverts or is rejected.
    /// @dev Emits an {IERC20-Transfer} event.
    /// @param to The account to transfer the tokens to.
    /// @param value The amount of tokens to transfer.
    /// @param data Optional additional data with no specified format, to be passed to the receiver contract.
    /// @return result Whether the operation succeeded.
    function safeTransfer(address to, uint256 value, bytes calldata data) external returns (bool result);

    /// @notice Transfers an amount of tokens to a recipient from a specified address. If the recipient is a contract, calls `onERC20Received` on it.
    /// @dev Reverts if `to` is the zero address.
    /// @dev Reverts if `from` does not have at least `value` of balance.
    /// @dev Reverts if the sender is not `from` and does not have at least `value` of allowance by `from`.
    /// @dev Reverts if `to` is a contract and the call to `onERC20Received(address,address,uint256,bytes)` fails, reverts or is rejected.
    /// @dev Emits an {IERC20-Transfer} event.
    /// @dev Optionally emits an {IERC20-Approval} event if the sender is not `from` (non-standard).
    /// @param from The account which owns the tokens to transfer.
    /// @param to The account to transfer the tokens to.
    /// @param value The amount of tokens to transfer.
    /// @param data Optional additional data with no specified format, to be passed to the receiver contract.
    /// @return result Whether the operation succeeded.
    function safeTransferFrom(address from, address to, uint256 value, bytes calldata data) external returns (bool result);
}

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

import {IERC165} from "./interfaces/IERC165.sol";
import {InterfaceDetectionStorage} from "./libraries/InterfaceDetectionStorage.sol";

/// @title ERC165 Interface Detection Standard (immutable or proxiable version).
/// @dev This contract is to be used via inheritance in an immutable (non-proxied) or proxied implementation.
abstract contract InterfaceDetection is IERC165 {
    using InterfaceDetectionStorage for InterfaceDetectionStorage.Layout;

    /// @inheritdoc IERC165
    function supportsInterface(bytes4 interfaceId) external view returns (bool) {
        return InterfaceDetectionStorage.layout().supportsInterface(interfaceId);
    }
}

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

pragma solidity ^0.8.20;

/**
 * @dev Library of standard hash functions.
 *
 * _Available since v5.1._
 */
library Hashes {
    /**
     * @dev Commutative Keccak256 hash of a sorted pair of bytes32. Frequently used when working with merkle proofs.
     *
     * NOTE: Equivalent to the `standardNodeHash` in our https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
     */
    function commutativeKeccak256(bytes32 a, bytes32 b) internal pure returns (bytes32) {
        return a < b ? _efficientKeccak256(a, b) : _efficientKeccak256(b, a);
    }

    /**
     * @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory.
     */
    function _efficientKeccak256(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
        assembly ("memory-safe") {
            mstore(0x00, a)
            mstore(0x20, b)
            value := keccak256(0x00, 0x40)
        }
    }
}

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

import {IERC173} from "./../interfaces/IERC173.sol";
import {ContractOwnershipStorage} from "./../libraries/ContractOwnershipStorage.sol";
import {Context} from "@openzeppelin/contracts/utils/Context.sol";

/// @title ERC173 Contract Ownership Standard (proxiable version).
/// @dev See https://eips.ethereum.org/EIPS/eip-173
/// @dev This contract is to be used via inheritance in a proxied implementation.
/// @dev Note: This contract requires ERC165 (Interface Detection Standard).
abstract contract ContractOwnershipBase is IERC173, Context {
    using ContractOwnershipStorage for ContractOwnershipStorage.Layout;

    /// @inheritdoc IERC173
    function owner() public view virtual returns (address) {
        return ContractOwnershipStorage.layout().owner();
    }

    /// @inheritdoc IERC173
    function transferOwnership(address newOwner) public virtual {
        ContractOwnershipStorage.layout().transferOwnership(_msgSender(), newOwner);
    }
}

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

/// @notice Thrown when an account is not the contract owner but is required to.
/// @param account The account that was checked.
error NotContractOwner(address account);

/// @notice Thrown when an account is not the pending contract owner but is required to.
/// @param account The account that was checked.
error NotPendingContractOwner(address account);

/// @notice Thrown when an account is not the target contract owner but is required to.
/// @param targetContract The contract that was checked.
/// @param account The account that was checked.
error NotTargetContractOwner(address targetContract, address account);

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

/// @notice Thrown when the target contract is actually not a contract.
/// @param targetContract The contract that was checked
error TargetIsNotAContract(address targetContract);

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

/// @notice Emitted when the contract ownership changes.
/// @param previousOwner the previous contract owner.
/// @param newOwner the new contract owner.
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

/// @notice Emitted when a new contract owner is pending.
/// @param pendingOwner the address of the new contract owner.
event OwnershipTransferPending(address indexed pendingOwner);

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

/// @title ERC-173 Contract Ownership Standard (functions)
/// @dev See https://eips.ethereum.org/EIPS/eip-173
/// @dev Note: the ERC-165 identifier for this interface is 0x7f5828d0
interface IERC173 {
    /// @notice Sets the address of the new contract owner.
    /// @dev Reverts if the sender is not the contract owner.
    /// @dev Emits an {OwnershipTransferred} event if `newOwner` is different from the current contract owner.
    /// @param newOwner The address of the new contract owner. Using the zero address means renouncing ownership.
    function transferOwnership(address newOwner) external;

    /// @notice Gets the address of the contract owner.
    /// @return contractOwner The address of the contract owner.
    function owner() external view returns (address contractOwner);
}

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

library Address {
    /// @notice Checks if the address is a deployed smart contract.
    /// @param addr The address to check.
    /// @return hasBytecode True if `addr` is a deployed smart contract, false otherwise.
    function hasBytecode(address addr) internal view returns (bool) {
        uint256 size;
        assembly {
            size := extcodesize(addr)
        }
        return size != 0;
    }
}

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

import {InitializationPhaseAlreadyReached} from "./../errors/ProxyInitializationErrors.sol";
import {StorageSlot} from "@openzeppelin/contracts/utils/StorageSlot.sol";

/// @notice Multiple calls protection for storage-modifying proxy initialization functions.
library ProxyInitialization {
    /// @notice Sets the initialization phase during a storage-modifying proxy initialization function.
    /// @dev Reverts with {InitializationPhaseAlreadyReached} if `phase` has been reached already.
    /// @param storageSlot the storage slot where `phase` is stored.
    /// @param phase the initialization phase.
    function setPhase(bytes32 storageSlot, uint256 phase) internal {
        StorageSlot.Uint256Slot storage currentVersion = StorageSlot.getUint256Slot(storageSlot);
        uint256 currentPhase = currentVersion.value;
        if (currentPhase >= phase) revert InitializationPhaseAlreadyReached(currentPhase, phase);
        currentVersion.value = phase;
    }
}

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

import {IllegalInterfaceId} from "./../errors/InterfaceDetectionErrors.sol";
import {IERC165} from "./../interfaces/IERC165.sol";

library InterfaceDetectionStorage {
    struct Layout {
        mapping(bytes4 => bool) supportedInterfaces;
    }

    bytes32 internal constant LAYOUT_STORAGE_SLOT = bytes32(uint256(keccak256("animoca.core.introspection.InterfaceDetection.storage")) - 1);

    bytes4 internal constant ILLEGAL_INTERFACE_ID = 0xffffffff;

    /// @notice Sets or unsets an ERC165 interface.
    /// @dev Revertswith {IllegalInterfaceId} if `interfaceId` is `0xffffffff`.
    /// @param interfaceId the interface identifier.
    /// @param supported True to set the interface, false to unset it.
    function setSupportedInterface(Layout storage s, bytes4 interfaceId, bool supported) internal {
        if (interfaceId == ILLEGAL_INTERFACE_ID) revert IllegalInterfaceId();
        s.supportedInterfaces[interfaceId] = supported;
    }

    /// @notice Returns whether this contract implements a given interface.
    /// @dev Note: This function call must use less than 30 000 gas.
    /// @param interfaceId The interface identifier to test.
    /// @return supported True if the interface is supported, false if `interfaceId` is `0xffffffff` or if the interface is not supported.
    function supportsInterface(Layout storage s, bytes4 interfaceId) internal view returns (bool supported) {
        if (interfaceId == ILLEGAL_INTERFACE_ID) {
            return false;
        }
        if (interfaceId == type(IERC165).interfaceId) {
            return true;
        }
        return s.supportedInterfaces[interfaceId];
    }

    function layout() internal pure returns (Layout storage s) {
        bytes32 position = LAYOUT_STORAGE_SLOT;
        assembly {
            s.slot := position
        }
    }
}

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

/// @title ERC165 Interface Detection Standard.
/// @dev See https://eips.ethereum.org/EIPS/eip-165.
/// @dev Note: The ERC-165 identifier for this interface is 0x01ffc9a7.
interface IERC165 {
    /// @notice Returns whether this contract implements a given interface.
    /// @dev Note: This function call must use less than 30 000 gas.
    /// @param interfaceId the interface identifier to test.
    /// @return supported True if the interface is supported, false if `interfaceId` is `0xffffffff` or if the interface is not supported.
    function supportsInterface(bytes4 interfaceId) external view returns (bool supported);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

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

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

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

/// @notice Emitted when trying to set a phase value that has already been reached.
/// @param currentPhase The current phase.
/// @param newPhase The new phase trying to be set.
error InitializationPhaseAlreadyReached(uint256 currentPhase, uint256 newPhase);

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.

pragma solidity ^0.8.20;

/**
 * @dev Library for reading and writing primitive types to specific storage slots.
 *
 * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
 * This library helps with reading and writing to such slots without the need for inline assembly.
 *
 * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
 *
 * Example usage to set ERC-1967 implementation slot:
 * ```solidity
 * contract ERC1967 {
 *     // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
 *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
 *
 *     function _getImplementation() internal view returns (address) {
 *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
 *     }
 *
 *     function _setImplementation(address newImplementation) internal {
 *         require(newImplementation.code.length > 0);
 *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
 *     }
 * }
 * ```
 *
 * TIP: Consider using this library along with {SlotDerivation}.
 */
library StorageSlot {
    struct AddressSlot {
        address value;
    }

    struct BooleanSlot {
        bool value;
    }

    struct Bytes32Slot {
        bytes32 value;
    }

    struct Uint256Slot {
        uint256 value;
    }

    struct Int256Slot {
        int256 value;
    }

    struct StringSlot {
        string value;
    }

    struct BytesSlot {
        bytes value;
    }

    /**
     * @dev Returns an `AddressSlot` with member `value` located at `slot`.
     */
    function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `BooleanSlot` with member `value` located at `slot`.
     */
    function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Bytes32Slot` with member `value` located at `slot`.
     */
    function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Uint256Slot` with member `value` located at `slot`.
     */
    function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Int256Slot` with member `value` located at `slot`.
     */
    function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `StringSlot` with member `value` located at `slot`.
     */
    function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
     */
    function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
        assembly ("memory-safe") {
            r.slot := store.slot
        }
    }

    /**
     * @dev Returns a `BytesSlot` with member `value` located at `slot`.
     */
    function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
     */
    function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
        assembly ("memory-safe") {
            r.slot := store.slot
        }
    }
}

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

/// @notice Thrown when setting the illegal interfaceId 0xffffffff.
error IllegalInterfaceId();

Settings
{
  "optimizer": {
    "enabled": true,
    "mode": "3"
  },
  "evmVersion": "paris",
  "outputSelection": {
    "*": {
      "*": [
        "abi"
      ]
    }
  },
  "detectMissingLibraries": false,
  "forceEVMLA": false,
  "enableEraVMExtensions": false,
  "codegen": "yul",
  "libraries": {}
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"rewardToken_","type":"address"},{"internalType":"address","name":"stakingPool_","type":"address"},{"internalType":"address","name":"tokenHolderWallet_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"bytes32","name":"epochId","type":"bytes32"},{"internalType":"bytes32","name":"leaf","type":"bytes32"}],"name":"AlreadyClaimed","type":"error"},{"inputs":[{"internalType":"bytes32","name":"epochId","type":"bytes32"}],"name":"EpochIdAlreadyExists","type":"error"},{"inputs":[{"internalType":"bytes32","name":"epochId","type":"bytes32"}],"name":"EpochIdNotExists","type":"error"},{"inputs":[],"name":"IllegalInterfaceId","type":"error"},{"inputs":[{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"uint256","name":"currentTime","type":"uint256"}],"name":"InvalidClaimWindow","type":"error"},{"inputs":[],"name":"InvalidMerkleRoot","type":"error"},{"inputs":[{"internalType":"bytes32","name":"epochId","type":"bytes32"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"InvalidProof","type":"error"},{"inputs":[],"name":"InvalidRewardToken","type":"error"},{"inputs":[],"name":"InvalidStakingPool","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"NotContractOwner","type":"error"},{"inputs":[{"internalType":"bytes32","name":"epochId","type":"bytes32"},{"internalType":"uint256","name":"currentTime","type":"uint256"}],"name":"OutOfClaimWindow","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"epochId","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"merkleRoot","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"startTime","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"endTime","type":"uint256"}],"name":"EpochMerkleRootSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"epochId","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"root","type":"bytes32"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"PayoutClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newTokenHolderWallet","type":"address"}],"name":"TokenHolderWalletSet","type":"event"},{"inputs":[],"name":"REWARD_TOKEN","outputs":[{"internalType":"contract IERC20SafeTransfers","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_POOL","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"epochId","type":"bytes32"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"canClaim","outputs":[{"internalType":"enum ERC20ClaimWindowMerkleClaimzk.ClaimError","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"epochId","type":"bytes32"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"}],"name":"claimAndStake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"epochId","type":"bytes32"}],"name":"claimWindows","outputs":[{"internalType":"bytes32","name":"merkleRoot","type":"bytes32"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"leaf","type":"bytes32"}],"name":"claimed","outputs":[{"internalType":"bool","name":"claimed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"epochId","type":"bytes32"},{"internalType":"bytes32","name":"merkleRoot","type":"bytes32"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"}],"name":"setEpochMerkleRoot","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newTokenHolderWallet","type":"address"}],"name":"setTokenHolderWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenHolderWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]

0x9c4d535b000000000000000000000000000000000000000000000000000000000000000001000157f06769445bb760b160faa979dc4d0e2c56b5e40b29e72d09fe43fb36000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000600000000000000000000000002226444afcccf9e760c01649ef1f9e66985a4b350000000000000000000000009a4840da1f12a13bf5fe32d9706c751b836d29d1000000000000000000000000ea215dbe8170b7aa4f19314f104bf883c798b90a

Deployed Bytecode

0x0001000000000002000d000000000002000000000801034f0000000000010355000000600110027000000112011001970000000100200190000000230000c13d0000008002000039000000400020043f000000040010008c0000036f0000413d000000000208043b000000e002200270000001220020009c000000750000213d0000012a0020009c000000940000213d0000012e0020009c000000b30000613d0000012f0020009c000001420000613d000001300020009c0000036f0000c13d0000000001000416000000000001004b0000036f0000c13d0000000001000412000c00000001001d000b00200000003d0000800501000039000000440300003900000000040004150000000c0440008a000000a40000013d0000000002000416000000000002004b0000036f0000c13d0000001f021000390000011302200197000000c002200039000000400020043f0000001f0310018f0000011404100198000000c002400039000000340000613d000000c005000039000000000608034f000000006706043c0000000005750436000000000025004b000000300000c13d000000000003004b000000410000613d000000000448034f0000000303300210000000000502043300000000053501cf000000000535022f000000000404043b0000010003300089000000000434022f00000000033401cf000000000353019f0000000000320435000000600010008c0000036f0000413d000000c00400043d000001150040009c0000036f0000213d000000e00700043d000001150070009c0000036f0000213d000001000500043d000001150050009c0000036f0000213d0000000006000411000000000006004b000000660000613d0000011601000041000000000201041a0000011702200197000000000262019f000000000021041b0000000001000414000001120010009c0000011201008041000000c00110021000000118011001c70000800d020000390000000303000039000700000004001d0000011904000041000800000005001d0000000005000019000600000007001d044204380000040f00000006070000290000000704000029000000080500002900000001002001900000036f0000613d0000011a01000041000000000010043f0000011b01000041000000200010043f0000011c01000041000000000201041a000001500220019700000001022001bf000000000021041b000000000004004b000001b50000c13d0000012101000041000000000010043f00000120010000410000044400010430000001230020009c000000a80000213d000001270020009c000000c10000613d000001280020009c0000015c0000613d000001290020009c0000036f0000c13d0000000002000416000000000002004b0000036f0000c13d000000240010008c0000036f0000413d0000000401800370000000000101043b000000000010043f0000000101000039000000200010043f00000040020000390000000001000019044204050000040f0000000202100039000000000202041a0000000103100039000000000303041a000000000101041a000000800010043f000000a00030043f000000c00020043f0000013401000041000004430001042e0000012b0020009c000000d50000613d0000012c0020009c000001850000613d0000012d0020009c0000036f0000c13d0000000001000416000000000001004b0000036f0000c13d0000000001000412000a00000001001d000900000000003d0000800501000039000000440300003900000000040004150000000a0440008a000000050440021000000136020000410442041a0000040f0000018a0000013d000001240020009c000001250000613d000001250020009c0000018e0000613d000001260020009c0000036f0000c13d0000000001000416000000000001004b0000036f0000c13d000000000100041a0000018a0000013d0000000002000416000000000002004b0000036f0000c13d000000240010008c0000036f0000413d0000000401800370000000000101043b0000014d001001980000036f0000c13d0000014e0010009c000001d80000a13d00000000010000190000008002000039000002330000013d0000000002000416000000000002004b0000036f0000c13d000000240010008c0000036f0000413d0000000401800370000000000101043b000000000010043f0000000201000039000000200010043f00000040020000390000000001000019044204050000040f000000000101041a000000ff001001900000000001000039000000010100c039000000800010043f0000013101000041000004430001042e0000000002000416000000000002004b0000036f0000c13d000000840010008c0000036f0000413d0000000402800370000000000202043b000500000002001d0000002402800370000000000202043b000400000002001d000001150020009c0000036f0000213d0000004402800370000000000202043b000300000002001d0000006402800370000000000202043b000001370020009c0000036f0000213d0000002303200039000000000013004b0000036f0000813d0000000403200039000000000338034f000000000303043b000700000003001d000001370030009c0000036f0000213d00000007030000290000000503300210000600240020003d0000000602300029000000000012004b0000036f0000213d0000000501000029000000a00010043f00000004010000290000006001100210000000c00010043f0000000301000029000000d40010043f0000005401000039000000800010043f0000010001000039000000400010043f0000000001000414000001120010009c0000011201008041000000c00110021000000138011001c700008010020000390442043d0000040f00000001002001900000036f0000613d000000000101043b000200000001001d0000000501000029000000000010043f0000000101000039000000200010043f0000000001000414000001120010009c0000011201008041000000c00110021000000139011001c700008010020000390442043d0000040f00000001002001900000036f0000613d000000000201043b000000000102041a000000000001004b0000023a0000c13d0000014601000041000000000010043f0000000501000029000000040010043f000001330100004100000444000104300000000002000416000000000002004b0000036f0000c13d000000240010008c0000036f0000413d0000000401800370000000000501043b000001150050009c0000036f0000213d000000000100041100000115011001970000011602000041000000000202041a0000011502200197000000000021004b000001b00000c13d000000000100041a0000011701100197000000000151019f000000000010041b0000000001000414000001120010009c0000011201008041000000c00110021000000118011001c70000800d0200003900000002030000390000011d04000041000001ab0000013d0000000002000416000000000002004b0000036f0000c13d000000840010008c0000036f0000413d0000006401800370000000000301043b0000004401800370000000000601043b0000002401800370000000000501043b0000000401800370000000000401043b000000000100041100000115011001970000011602000041000000000202041a0000011502200197000000000021004b000001b00000c13d000000000005004b000001dd0000c13d0000014c01000041000000000010043f000001200100004100000444000104300000000002000416000000000002004b0000036f0000c13d000000640010008c0000036f0000413d0000002401800370000000000301043b000001150030009c0000036f0000213d0000000401800370000000000101043b000700000001001d000000000010043f0000000101000039000000200010043f000000400200003900000000010000190008000000080353000600000003001d044204050000040f000000080200035f0000004402200370000000000402043b000800000001001d000000a00100003900000007020000290000000603000029044203c60000040f000000a00210008a000000800020043f000000800210008a0000008001000039044203b40000040f000000800200043d000000a001000039044204050000040f00000000020100190000000801000029044203ce0000040f000000400200043d000002340000013d0000000001000416000000000001004b0000036f0000c13d0000011601000041000000000101041a0000011501100197000000800010043f0000013101000041000004430001042e0000000002000416000000000002004b0000036f0000c13d000000240010008c0000036f0000413d0000000401800370000000000601043b000001150060009c0000036f0000213d0000011601000041000000000101041a00000115021001970000000005000411000000000025004b0000020e0000c13d000000000065004b000001ae0000613d0000011701100197000000000161019f0000011602000041000000000012041b0000000001000414000001120010009c0000011201008041000000c00110021000000118011001c70000800d0200003900000003030000390000011904000041044204380000040f00000001002001900000036f0000613d0000000001000019000004430001042e0000013202000041000000000020043f000000040010043f00000133010000410000044400010430000000800040043f000000000007004b000001bc0000c13d0000011f01000041000000000010043f00000120010000410000044400010430000000a00070043f000000000100041a0000011701100197000000000151019f000000000010041b0000000001000414000001120010009c0000011201008041000000c00110021000000118011001c70000800d0200003900000002030000390000011d04000041044204380000040f00000001002001900000036f0000613d000000800100043d00000140000004430000016000100443000000a00100043d00000020020000390000018000200443000001a0001004430000010000200443000000020100003900000120001004430000011e01000041000004430001042e0000014f0010009c000002210000c13d00000001010000390000008002000039000002330000013d000000000036004b000002130000813d000600000006001d000500000005001d000800000004001d000700000003001d0000013a0100004100000000001004430000000001000414000001120010009c0000011201008041000000c0011002100000013b011001c70000800b020000390442043d0000040f0000000100200190000003710000613d000000000101043b0000000703000029000000000013004b00000008010000290000000606000029000002130000a13d000000000010043f0000000101000039000000200010043f0000000001000414000001120010009c0000011201008041000000c00110021000000139011001c700008010020000390442043d0000040f00000001002001900000036f0000613d000000000101043b000000000101041a000000000001004b0000025f0000c13d000000400100043d000400000001001d000001490010009c000002650000413d0000014b01000041000000000010043f0000004101000039000000040010043f000001330100004100000444000104300000013201000041000000000010043f000000040050043f000001330100004100000444000104300000014701000041000000000010043f000000040060043f000000240030043f0000800b01000039000000040300003900000000040004150000000d0440008a00000005044002100000013a020000410442041a0000040f000000440010043f0000013f010000410000044400010430000000000010043f0000011b01000041000000200010043f0000000001000414000001120010009c0000011201008041000000c00110021000000139011001c700008010020000390442043d0000040f00000001002001900000036f0000613d000000400200043d000000000101043b000000000101041a000000ff001001900000000001000039000000010100c039000000010110018f0000000000120435000001120020009c0000011202008041000000400120021000000135011001c7000004430001042e000100000002001d0000000101200039000000000101041a000800000001001d0000013a0100004100000000001004430000000001000414000001120010009c0000011201008041000000c0011002100000013b011001c70000800b020000390442043d0000040f0000000100200190000003710000613d000000000101043b000000080010006c000002510000413d00000001020000290000000202200039000000000202041a000000000021004b0000029d0000a13d0000014501000041000000000010043f0000000501000029000000040010043f0000800b01000039000000040300003900000000040004150000000d0440008a00000005044002100000013a020000410442041a0000040f000000240010043f0000013d0100004100000444000104300000014801000041000000000010043f0000000801000029000000040010043f0000013301000041000004440001043000000004020000290000006001200039000000400010043f00000040032000390000000701000029000300000003001d0000000000130435000000050100002900000000021204360000000601000029000200000002001d00000000001204350000000801000029000000000010043f0000000101000039000000200010043f0000000001000414000001120010009c0000011201008041000000c00110021000000139011001c700008010020000390442043d0000040f00000001002001900000036f0000613d00000004020000290000000002020433000000000101043b000000000021041b000000020200002900000000020204330000000103100039000000000023041b000000020110003900000003020000290000000002020433000000000021041b000000400100043d00000006020000290000000000210435000001120010009c000001120100804100000040011002100000000002000414000001120020009c0000011202008041000000c002200210000000000112019f00000143011001c70000800d0200003900000004030000390000014a04000041000000080500002900000005060000290000000707000029000001ab0000013d0000000201000029000000000010043f0000000201000039000000200010043f0000000001000414000001120010009c0000011201008041000000c00110021000000139011001c700008010020000390442043d0000040f00000001002001900000036f0000613d000000000101043b000000000101041a000000ff00100190000003720000c13d0000000101000029000000000101041a000100000001001d000000070000006b0000000201000029000002d10000613d00000000030000190000000201000029000800000003001d000000050230021000000006022000290000000002200367000000000202043b000000000021004b000002c10000813d000000000010043f000000200020043f0000000001000414000002c40000013d000000000020043f000000200010043f0000000001000414000001120010009c0000011201008041000000c00110021000000139011001c700008010020000390442043d0000040f00000001002001900000036f0000613d0000000803000029000000000101043b0000000103300039000000070030006c000002b60000413d000000010010006c0000037a0000c13d0000000201000029000000000010043f0000000201000039000000200010043f0000000001000414000001120010009c0000011201008041000000c00110021000000139011001c700008010020000390442043d0000040f00000001002001900000036f0000613d000000000101043b000000000201041a000001500220019700000001022001bf000000000021041b000000000200041a000000400500043d000000200100003900000000041504360000000403000029000700000004001d0000000000340435000800000005001d000001400050009c000002080000213d00000008050000290000004004500039000000400040043f0000014103000041000600000004001d0000000000340435000001150220019700000044035000390000000000230435000001360200004100000000002004430000000002000412000000040020044300000024001004430000000001000414000001120010009c0000011201008041000000c00110021000000142011001c700008005020000390442043d0000040f0000000100200190000003710000613d000000000101043b0000000804000029000000a402400039000000800300003900000000003204350000008402400039000000030300002900000000003204350000011501100197000000640240003900000000001204350000000006040433000000c4014000390000000000610435000000e401400039000000000006004b0000000705000029000003200000613d000000000200001900000000032100190000000004250019000000000404043300000000004304350000002002200039000000000062004b000003190000413d000800000006001d00000000016100190000000000010435000001360100004100000000001004430000000001000412000000040010044300000024000004430000000001000414000001120010009c0000011201008041000000c00110021000000142011001c700008005020000390442043d0000040f0000000100200190000003710000613d00000008020000290000001f022000390000015102200197000000a402200039000001120020009c000001120200804100000060022002100000000603000029000001120030009c00000112030080410000004003300210000000000232019f000000000301043b0000000001000414000001120010009c0000011201008041000000c001100210000000000121019f0000011502300197044204380000040f00000060031002700000011203300197000000200030008c000000200400003900000000040340190000001f0640018f00000020074001900000000605700029000003540000613d000000000801034f0000000609000029000000008a08043c0000000009a90436000000000059004b000003500000c13d000000000006004b000003610000613d000000000771034f0000000306600210000000000805043300000000086801cf000000000868022f000000000707043b0000010006600089000000000767022f00000000066701cf000000000686019f00000000006504350000000100200190000003840000613d0000001f01400039000000600110018f0000000601100029000001370010009c000002080000213d000000400010043f000000200030008c0000036f0000413d00000006020000290000000002020433000000010020008c000003a20000a13d00000000010000190000044400010430000000000001042f0000013c01000041000000000010043f0000000501000029000000040010043f0000000201000029000000240010043f0000013d0100004100000444000104300000013e01000041000000000010043f0000000501000029000000040010043f0000000401000029000000240010043f0000000301000029000000440010043f0000013f0100004100000444000104300000001f0530018f0000011406300198000000400200043d00000000046200190000038f0000613d000000000701034f0000000008020019000000007907043c0000000008980436000000000048004b0000038b0000c13d000000000005004b0000039c0000613d000000000161034f0000000305500210000000000604043300000000065601cf000000000656022f000000000101043b0000010005500089000000000151022f00000000015101cf000000000161019f00000000001404350000006001300210000001120020009c00000112020080410000004002200210000000000112019f000004440001043000000003020000290000000000210435000001120010009c000001120100804100000040011002100000000002000414000001120020009c0000011202008041000000c002200210000000000112019f00000143011001c70000800d0200003900000004030000390000014404000041000000050500002900000001060000290000000407000029000001ab0000013d0000001f0220003900000151022001970000000001120019000000000021004b00000000020000390000000102004039000001370010009c000003c00000213d0000000100200190000003c00000c13d000000400010043f000000000001042d0000014b01000041000000000010043f0000004101000039000000040010043f000001330100004100000444000104300000006003300210000000200510003900000000003504350000003403100039000000000043043500000000002104350000005401100039000000000001042d0003000000000002000000000301041a000000000003004b000003ff0000613d000100000002001d000200000001001d0000000101100039000000000101041a000300000001001d0000013a0100004100000000001004430000000001000414000001120010009c0000011201008041000000c0011002100000013b011001c70000800b020000390442043d0000040f0000000100200190000004010000613d000000000301034f0000000201000039000000000203043b000000030020006c000004000000413d00000002030000290000000203300039000000000303041a000000000032004b000004000000213d0000000101000029000000000010043f0000000201000039000000200010043f0000000001000414000001120010009c0000011201008041000000c00110021000000139011001c700008010020000390442043d0000040f0000000100200190000004020000613d000000000101043b000000000101041a000000ff0010019000000003010000390000000001006039000000000001042d0000000101000039000000000001042d000000000001042f00000000010000190000044400010430000000000001042f000001120010009c00000112010080410000004001100210000001120020009c00000112020080410000006002200210000000000112019f0000000002000414000001120020009c0000011202008041000000c002200210000000000112019f00000118011001c700008010020000390442043d0000040f0000000100200190000004180000613d000000000101043b000000000001042d0000000001000019000004440001043000000000050100190000000000200443000000050030008c000004280000413d000000040100003900000000020000190000000506200210000000000664001900000005066002700000000006060031000000000161043a0000000102200039000000000031004b000004200000413d000001120030009c000001120300804100000060013002100000000002000414000001120020009c0000011202008041000000c002200210000000000112019f00000152011001c700000000020500190442043d0000040f0000000100200190000004370000613d000000000101043b000000000001042d000000000001042f0000043b002104210000000102000039000000000001042d0000000002000019000000000001042d00000440002104230000000102000039000000000001042d0000000002000019000000000001042d0000044200000432000004430001042e000004440001043000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000001ffffffe000000000000000000000000000000000000000000000000000000000ffffffe0000000000000000000000000ffffffffffffffffffffffffffffffffffffffffc9ed16f33ab3a66c84bfd83099ccb2a8845871e2e1c1928f63797152f0fd54ccffffffffffffffffffffffff000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000008be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e07f5828d000000000000000000000000000000000000000000000000000000000ca9d3e17f264b0f3984e2634e94adb37fa3e6a8103f06aeae6fa59e21c769f5da53d5a615b1d5279f1a6647c5c0f31bf064b1e70af135676a4a96aeaeb691a2f329827290d5ca169f07779e969ad40b6df3087f6c12c9467c96f1a88316a5bdb00000002000000000000000000000000000000c00000010000000000000000003b106f85000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000dfde86710000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cc3c0f0500000000000000000000000000000000000000000000000000000000ec7f749300000000000000000000000000000000000000000000000000000000ec7f749400000000000000000000000000000000000000000000000000000000f2fde38b00000000000000000000000000000000000000000000000000000000fbb0f67300000000000000000000000000000000000000000000000000000000cc3c0f0600000000000000000000000000000000000000000000000000000000d6a3138b00000000000000000000000000000000000000000000000000000000dc45def600000000000000000000000000000000000000000000000000000000563cac2700000000000000000000000000000000000000000000000000000000563cac28000000000000000000000000000000000000000000000000000000008da5cb5b0000000000000000000000000000000000000000000000000000000099248ea70000000000000000000000000000000000000000000000000000000001ffc9a7000000000000000000000000000000000000000000000000000000000328a8fc0000000000000000000000000000000000000000000000000000000014ae36ae00000000000000000000000000000000000000200000008000000000000000002ef4875e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000600000008000000000000000000000000000000000000000000000000000000020000000000000000000000000310ab089e4439a4c15d089f94afb7896ff553aecb10793d0ab882de59d99a32e000000000000000000000000000000000000000000000000ffffffffffffffff0200000000000000000000000000000000000054000000a000000000000000000200000000000000000000000000000000000040000000000000000000000000796b89b91644bc98cd93958e4c9038275d622183e25ac5af08cc6b5d955391320200000200000000000000000000000000000004000000000000000000000000dc20789f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044000000000000000000000000cc55900d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffbfb88d4fde000000000000000000000000000000000000000000000000000000000200000200000000000000000000000000000044000000000000000000000000020000000000000000000000000000000000002000000000000000000000000006da15703c9baa6922f6a2f40fe90a03f70a24b9113edd75f913454a45d996cddbeb4f4d0000000000000000000000000000000000000000000000000000000084f229dc00000000000000000000000000000000000000000000000000000000790c7cef00000000000000000000000000000000000000000000000000000000ed10979d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffa097df4a2fb6656595bc6eb616c2068e9d50031428ef69d8849590bc9f3dec620b4e487b71000000000000000000000000000000000000000000000000000000009dd854d30000000000000000000000000000000000000000000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff01ffc9a700000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe002000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a2646970667358221220b9ba7b142ec422daa9c543433e45c613d133143ae3b66b950f13188f0e08fc1064736f6c6378247a6b736f6c633a312e352e31353b736f6c633a302e382e32383b6c6c766d3a312e302e320055

Block Transaction Gas Used Reward
view all blocks produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]

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.