false
true
0

Contract Address Details

0xFe53f25c164C3C73aD75bdeB17Ed3364d7544AFC

Token
Sway (SWAY)
Creator
0x792d57–08d0c3 at 0xf34a92–c5c08c
Balance
0 PLS ( )
Tokens
Fetching tokens...
Transactions
279 Transactions
Transfers
0 Transfers
Gas Used
0
Last Balance Update
26124794
Warning! Contract bytecode has been changed and doesn't match the verified one. Therefore, interaction with this smart contract may be risky.
Contract name:
Oase




Optimization enabled
true
Compiler version
v0.8.20+commit.a1b79de6




Optimization runs
200
EVM Version
default




Verified at
2025-10-07T08:51:11.235441Z

Constructor Arguments

0x45711452762d9dab167c16cc1f8672d6b3102251471bbf07113c1e1134d39eea

Arg [0] (bytes32) : 45711452762d9dab167c16cc1f8672d6b3102251471bbf07113c1e1134d39eea

              

Contract source code

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0 ^0.8.20;

// npm/@openzeppelin/contracts@v4.9.6/token/ERC20/IERC20.sol

// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

// npm/@openzeppelin/contracts@v4.9.6/utils/Context.sol

// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)

/**
 * @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;
    }
}

// npm/@openzeppelin/contracts@v5.0.0/utils/cryptography/ECDSA.sol

// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/ECDSA.sol)

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSA {
    enum RecoverError {
        NoError,
        InvalidSignature,
        InvalidSignatureLength,
        InvalidSignatureS
    }

    /**
     * @dev The signature derives the `address(0)`.
     */
    error ECDSAInvalidSignature();

    /**
     * @dev The signature has an invalid length.
     */
    error ECDSAInvalidSignatureLength(uint256 length);

    /**
     * @dev The signature has an S value that is in the upper half order.
     */
    error ECDSAInvalidSignatureS(bytes32 s);

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not
     * return address(0) without also returning an error description. Errors are documented using an enum (error type)
     * and a bytes32 providing additional information about the error.
     *
     * If no error is returned, then the address can be used for verification purposes.
     *
     * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
     *
     * Documentation for signature generation:
     * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
     * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
     */
    function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError, bytes32) {
        if (signature.length == 65) {
            bytes32 r;
            bytes32 s;
            uint8 v;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            /// @solidity memory-safe-assembly
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
            return tryRecover(hash, v, r, s);
        } else {
            return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
        _throwError(error, errorArg);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
     *
     * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
     */
    function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError, bytes32) {
        unchecked {
            bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
            // We do not check for an overflow here since the shift operation results in 0 or 1.
            uint8 v = uint8((uint256(vs) >> 255) + 27);
            return tryRecover(hash, v, r, s);
        }
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
     */
    function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);
        _throwError(error, errorArg);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function tryRecover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address, RecoverError, bytes32) {
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            return (address(0), RecoverError.InvalidSignatureS, s);
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        if (signer == address(0)) {
            return (address(0), RecoverError.InvalidSignature, bytes32(0));
        }

        return (signer, RecoverError.NoError, bytes32(0));
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);
        _throwError(error, errorArg);
        return recovered;
    }

    /**
     * @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.
     */
    function _throwError(RecoverError error, bytes32 errorArg) private pure {
        if (error == RecoverError.NoError) {
            return; // no error: do nothing
        } else if (error == RecoverError.InvalidSignature) {
            revert ECDSAInvalidSignature();
        } else if (error == RecoverError.InvalidSignatureLength) {
            revert ECDSAInvalidSignatureLength(uint256(errorArg));
        } else if (error == RecoverError.InvalidSignatureS) {
            revert ECDSAInvalidSignatureS(errorArg);
        }
    }
}

// npm/@openzeppelin/contracts@v5.0.0/utils/cryptography/MerkleProof.sol

// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MerkleProof.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.
 */
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.
     */
    function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        return processProof(proof, leaf) == root;
    }

    /**
     * @dev Calldata version of {verify}
     */
    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 leafs & pre-images are assumed to be sorted.
     */
    function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = _hashPair(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Calldata version of {processProof}
     */
    function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = _hashPair(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}.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     */
    function multiProofVerify(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProof(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Calldata version of {multiProofVerify}
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     */
    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.
     *
     * 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).
     */
    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 proofLen = proof.length;
        uint256 totalHashes = proofFlags.length;

        // Check proof validity.
        if (leavesLen + proofLen != totalHashes + 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[](totalHashes);
        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 < totalHashes; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = _hashPair(a, b);
        }

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

    /**
     * @dev Calldata version of {processMultiProof}.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     */
    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 proofLen = proof.length;
        uint256 totalHashes = proofFlags.length;

        // Check proof validity.
        if (leavesLen + proofLen != totalHashes + 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[](totalHashes);
        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 < totalHashes; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = _hashPair(a, b);
        }

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

    /**
     * @dev Sorts the pair (a, b) and hashes the result.
     */
    function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
        return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
    }

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

// npm/@openzeppelin/contracts@v5.0.0/utils/math/Math.sol

// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    /**
     * @dev Muldiv operation overflow.
     */
    error MathOverflowedMulDiv();

    enum Rounding {
        Floor, // Toward negative infinity
        Ceil, // Toward positive infinity
        Trunc, // Toward zero
        Expand // Away from zero
    }

    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds towards infinity instead
     * of rounding towards zero.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        if (b == 0) {
            // Guarantee the same behavior as in a regular Solidity division.
            return a / b;
        }

        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
     * denominator == 0.
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
     * Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0 = x * y; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            if (denominator <= prod1) {
                revert MathOverflowedMulDiv();
            }

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator.
            // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.

            uint256 twos = denominator & (0 - denominator);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
            // works in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
     * towards zero.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
        }
    }

    /**
     * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
     */
    function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
        return uint8(rounding) % 2 == 1;
    }
}

// npm/@openzeppelin/contracts@v5.0.0/utils/math/SignedMath.sol

// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol)

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMath {
    /**
     * @dev Returns the largest of two signed numbers.
     */
    function max(int256 a, int256 b) internal pure returns (int256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two signed numbers.
     */
    function min(int256 a, int256 b) internal pure returns (int256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
    function average(int256 a, int256 b) internal pure returns (int256) {
        // Formula from the book "Hacker's Delight"
        int256 x = (a & b) + ((a ^ b) >> 1);
        return x + (int256(uint256(x) >> 255) & (a ^ b));
    }

    /**
     * @dev Returns the absolute unsigned value of a signed value.
     */
    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }
}

// npm/@openzeppelin/contracts@v4.9.6/token/ERC20/extensions/IERC20Metadata.sol

// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

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

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

// npm/@openzeppelin/contracts@v5.0.0/utils/Strings.sol

// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol)

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant HEX_DIGITS = "0123456789abcdef";
    uint8 private constant ADDRESS_LENGTH = 20;

    /**
     * @dev The `value` string doesn't fit in the specified `length`.
     */
    error StringsInsufficientHexLength(uint256 value, uint256 length);

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toStringSigned(int256 value) internal pure returns (string memory) {
        return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        uint256 localValue = value;
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = HEX_DIGITS[localValue & 0xf];
            localValue >>= 4;
        }
        if (localValue != 0) {
            revert StringsInsufficientHexLength(value, length);
        }
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
     * representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
    }

    /**
     * @dev Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
    }
}

// npm/@openzeppelin/contracts@v4.9.6/token/ERC20/ERC20.sol

// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol)

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * The default value of {decimals} is 18. To change this, you should override
 * this function so it returns a different value.
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC20
 * applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20 is Context, IERC20, IERC20Metadata {
    mapping(address => uint256) private _balances;

    mapping(address => mapping(address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the default value returned by this function, unless
     * it's overridden.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual override returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `amount`.
     */
    function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, allowance(owner, spender) + addedValue);
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        address owner = _msgSender();
        uint256 currentAllowance = allowance(owner, spender);
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        unchecked {
            _approve(owner, spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    /**
     * @dev Moves `amount` of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     */
    function _transfer(address from, address to, uint256 amount) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
            // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
            // decrementing then incrementing.
            _balances[to] += amount;
        }

        emit Transfer(from, to, amount);

        _afterTokenTransfer(from, to, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        unchecked {
            // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
            _balances[account] += amount;
        }
        emit Transfer(address(0), account, amount);

        _afterTokenTransfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        unchecked {
            _balances[account] = accountBalance - amount;
            // Overflow not possible: amount <= accountBalance <= totalSupply.
            _totalSupply -= amount;
        }

        emit Transfer(account, address(0), amount);

        _afterTokenTransfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @dev Updates `owner` s allowance for `spender` based on spent `amount`.
     *
     * Does not update the allowance amount in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Might emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "ERC20: insufficient allowance");
            unchecked {
                _approve(owner, spender, currentAllowance - amount);
            }
        }
    }

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}

    /**
     * @dev Hook that is called after any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * has been transferred to `to`.
     * - when `from` is zero, `amount` tokens have been minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
}

// npm/@openzeppelin/contracts@v5.0.0/utils/cryptography/MessageHashUtils.sol

// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MessageHashUtils.sol)

/**
 * @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing.
 *
 * The library provides methods for generating a hash of a message that conforms to the
 * https://eips.ethereum.org/EIPS/eip-191[EIP 191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712]
 * specifications.
 */
library MessageHashUtils {
    /**
     * @dev Returns the keccak256 digest of an EIP-191 signed data with version
     * `0x45` (`personal_sign` messages).
     *
     * The digest is calculated by prefixing a bytes32 `messageHash` with
     * `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the
     * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method.
     *
     * NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with
     * keccak256, although any bytes32 value can be safely used because the final digest will
     * be re-hashed.
     *
     * See {ECDSA-recover}.
     */
    function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash
            mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix
            digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20)
        }
    }

    /**
     * @dev Returns the keccak256 digest of an EIP-191 signed data with version
     * `0x45` (`personal_sign` messages).
     *
     * The digest is calculated by prefixing an arbitrary `message` with
     * `"\x19Ethereum Signed Message:\n" + len(message)` and hashing the result. It corresponds with the
     * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method.
     *
     * See {ECDSA-recover}.
     */
    function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) {
        return
            keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message));
    }

    /**
     * @dev Returns the keccak256 digest of an EIP-191 signed data with version
     * `0x00` (data with intended validator).
     *
     * The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended
     * `validator` address. Then hashing the result.
     *
     * See {ECDSA-recover}.
     */
    function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(hex"19_00", validator, data));
    }

    /**
     * @dev Returns the keccak256 digest of an EIP-712 typed data (EIP-191 version `0x01`).
     *
     * The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with
     * `\x19\x01` and hashing the result. It corresponds to the hash signed by the
     * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712.
     *
     * See {ECDSA-recover}.
     */
    function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) {
        /// @solidity memory-safe-assembly
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, hex"19_01")
            mstore(add(ptr, 0x02), domainSeparator)
            mstore(add(ptr, 0x22), structHash)
            digest := keccak256(ptr, 0x42)
        }
    }
}

// src/oase.sol

// All Rights Reserved

/**
 * @title Oase
 * @dev A savings token contract that provides a single saving/locking feature. The lock length is fixed at 369 days,
 *      and there is one global share rate that is recalculated daily.
 *
 *      The daily share rate growth is determined by a formula that depends on the ratio of the
 *      current token value of all locked shares to the conceptual total supply (circulating supply
 *      plus the current token value of all locked shares).
 *      For example, if 100% of the conceptual total supply (by value) is locked, the target annual rate
 *      for share growth is approximately 3.69%. If only 10% (by value) is locked, it's approximately 36.9%,
 *      and if 1% (by value) is locked, it's approximately 369%, and so on.
 *
 *      NOTE: All percentage references are annual. Daily growth is approximated by dividing the annual growth parameter by 365.
 *      This means the actual compounded Annual Percentage Rate (APR) is an approximation.
 */
contract Oase is ERC20 {
    // ------------------------------------------------------------------------
    // Global saving feature constants
    // ------------------------------------------------------------------------

    /**
     * @dev Single, fixed lock length in days (369 days).
     */
    uint256 public constant LOCK_LENGTH_DAYS = 369;

    /**
     * @dev Maximum number of days to process in a single _dailyDataUpdate call.
     */
    uint256 private constant MAX_DAYS_TO_UPDATE = 369;

    /**
     * @dev Start of contract in Unix time. For demonstration, set to block.timestamp 
     *      at deployment.
     */
    uint256 public immutable LAUNCH_TIME;

    // ------------------------------------------------------------------------
    // Global saving system variables
    // ------------------------------------------------------------------------

    /**
     * @dev Stores global variables for the contract's saving feature.
     *      It tracks the current shareRate, the number of days fully processed by dailyDataUpdate,
     *      the total amount of tokens initially deposited into active savings, and the total number of shares
     *      currently locked across all savings.
     */
    struct Globals {
        uint256 shareRate;         // Current share rate (scaled by 1e8)
        uint256 dailyDataCount;    // Number of days fully processed by dailyDataUpdate
        uint256 lockedTokenSupply; // Total tokens initially deposited and still locked
        uint256 totalSharesLocked; // Total shares currently locked across all savings
    }

    /**
     * @dev A copy of Globals in memory for calculations in each function call,
     *      to avoid repeated storage reads/writes.
     */
    struct GlobalsCache {
        uint256 _shareRate;
        uint256 _dailyDataCount;
        uint256 _lockedTokenSupply; // Total tokens initially deposited and still locked
        uint256 _totalSharesLocked; // Total shares currently locked
        uint256 _currentDay;       // We also keep track of the "current day" in memory
    }

    Globals public globals; // The single instance of our Globals struct in storage

    // ------------------------------------------------------------------------
    // Structures / Storage for daily share rate data
    // ------------------------------------------------------------------------
    struct DailyDataStore {
        uint256 dayShareRate;         // records what the shareRate was after that day's update
    }

    mapping(uint256 => DailyDataStore) public dailyData;

    // ------------------------------------------------------------------------
    // Structures / Storage for each saving
    // ------------------------------------------------------------------------
    struct SavingStore {
        uint40 savingId;           // Unique ID for this saving
        uint256 savedShares;       // Number of shares purchased at start
        uint256 savedTokens;       // Number of tokens burned at start
        uint256 savingStartDay;    // Day the saving starts
        uint256 savingEndDay;      // Day the saving can be ended
    }

    /**
     * @dev We keep a unique ID for each new saving across all addresses
     */
    uint40 public latestSavingId;

    /**
     * @dev Each user can have multiple savings
     */
    mapping(address => SavingStore[]) public savingsLists;

    /**
     * @dev Merkle root for verification of claim eligibility 
     */
    bytes32 public immutable merkleRoot;

    /**
     * @dev Tracks whether a claim ID has been used
     */
    mapping(uint256 => bool) public hasClaimedId;

    // ------------------------------------------------------------------------
    // Events
    // ------------------------------------------------------------------------
    event DailyDataUpdate(
        uint256 indexed timestamp,
        uint256 indexed currentDay,
        uint256 newShareRate
    );

    event SavingStart(
        uint256 indexed data,         // Packed data of (block.timestamp, savedTokens, savedShares)
        address indexed saverAddr,
        uint40 indexed savingId
    );

    event SavingEnd(
        uint256 indexed data,         // Packed data of (block.timestamp, savedShares, payoutTokens)
        address indexed saverAddr,
        uint40 indexed savingId
    );

    /**
     * @dev Emitted when a claim is successfully processed
     */
    event Claimed(
        uint256 indexed id,
        address indexed account, 
        uint256 amount, 
        uint256 timestamp
    );

    // ------------------------------------------------------------------------
    // Constructor
    // ------------------------------------------------------------------------
    constructor(bytes32 _merkleRoot) 
        ERC20("Sway", "SWAY")  // Name and Symbol via OpenZeppelin's constructor
    {
        merkleRoot = _merkleRoot;
        LAUNCH_TIME = block.timestamp;

        // Initialize our persistent storage
        globals.shareRate = 1e8;       // Start shareRate at 1.0 (scaled by 1e8)
        globals.dailyDataCount = 0;
        globals.lockedTokenSupply = 0; // Initialize total tokens locked
        globals.totalSharesLocked = 0; // Initialize new total shares locked
    }

    // ------------------------------------------------------------------------
    // ERC20 Overrides
    // ------------------------------------------------------------------------

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * Overrides the default 18 decimals to use 8 decimals.
     */
    function decimals() public view virtual override returns (uint8) {
        return 8;
    }

    // ------------------------------------------------------------------------
    // GlobalsCache load/sync
    // ------------------------------------------------------------------------

    /**
     * @dev Load global values into a memory-based cache so we can manipulate them
     *      without constantly reading from storage.
     */
    function _globalsLoad(GlobalsCache memory g, GlobalsCache memory gSnapshot)
        internal
        view
    {
        g._shareRate         = globals.shareRate;
        g._dailyDataCount    = globals.dailyDataCount;
        g._lockedTokenSupply = globals.lockedTokenSupply; // Load total tokens locked
        g._totalSharesLocked = globals.totalSharesLocked; // Load total shares locked
        g._currentDay        = _currentDay();

        _globalsCacheSnapshot(g, gSnapshot);
    }

    /**
     * @dev Take a snapshot of the current memory-based GlobalsCache.
     *      We'll compare with this snapshot to see if anything changed
     *      before writing back to storage.
     */
    function _globalsCacheSnapshot(GlobalsCache memory g, GlobalsCache memory gSnapshot)
        private
        pure
    {
        gSnapshot._shareRate         = g._shareRate;
        gSnapshot._dailyDataCount    = g._dailyDataCount;
        gSnapshot._lockedTokenSupply = g._lockedTokenSupply; // Snapshot total tokens locked
        gSnapshot._totalSharesLocked = g._totalSharesLocked; // Snapshot total shares locked
        gSnapshot._currentDay        = g._currentDay;
    }

    /**
     * @dev Compare memory-based GlobalsCache to the snapshot. If anything changed,
     *      write the updated values back to storage. This saves gas by storing only
     *      if changes occurred.
     */
    function _globalsSync(GlobalsCache memory g, GlobalsCache memory gSnapshot)
        internal
    {
        // If any relevant field changed, update
        if (
            g._shareRate         != gSnapshot._shareRate
         || g._dailyDataCount    != gSnapshot._dailyDataCount
         || g._lockedTokenSupply != gSnapshot._lockedTokenSupply // Check total tokens locked
         || g._totalSharesLocked != gSnapshot._totalSharesLocked // Check total shares locked
        ) {
            globals.shareRate         = g._shareRate;
            globals.dailyDataCount    = g._dailyDataCount;
            globals.lockedTokenSupply = g._lockedTokenSupply; // Sync total tokens locked
            globals.totalSharesLocked = g._totalSharesLocked; // Sync total shares locked
        }
    }

    // ------------------------------------------------------------------------
    // Time / Day Utilities
    // ------------------------------------------------------------------------
    function _currentDay() internal view returns (uint256) {
        // Each "day" is a 24-hour period from LAUNCH_TIME
        return (block.timestamp - LAUNCH_TIME) / 1 days;
    }

    // ------------------------------------------------------------------------
    // Internal daily data update logic
    // ------------------------------------------------------------------------
    /**
     * @dev Processes daily updates to the share rate. It iterates day-by-day from the last fully processed day
     *      (`g._dailyDataCount`) up to (but not including) the target `beforeDay`. In each iteration for a `currentDay`,
     *      it first records the existing `g._shareRate` (representing the rate at the start of `currentDay + 1`)
     *      into `dailyData[currentDay + 1]`. Then, it calculates and applies the share rate growth for `currentDay`
     *      to `g._shareRate`. The loop runs for a maximum of MAX_DAYS_TO_UPDATE days per call.
     * @param g The GlobalsCache memory struct, containing current global values.
     * @param beforeDay The target day to process updates up to (exclusive).
     * @return completed True if the update process reached `beforeDay`, false otherwise (e.g., if MAX_DAYS_TO_UPDATE limit was hit).
     */
    function _dailyDataUpdate(GlobalsCache memory g, uint256 beforeDay) private returns (bool completed) {
        uint256 daysToProcess = beforeDay > g._dailyDataCount ? beforeDay - g._dailyDataCount : 0;

        if (daysToProcess == 0) {
            return true; // Already up-to-date
        }

        uint256 daysThisRun = daysToProcess > MAX_DAYS_TO_UPDATE ? MAX_DAYS_TO_UPDATE : daysToProcess;
        uint256 endDayForLoop = g._dailyDataCount + daysThisRun;

        // Process each day from dailyDataCount up to endDayForLoop
        for (uint256 currentDay = g._dailyDataCount; currentDay < endDayForLoop; currentDay++) {
            // 1) Record the current shareRate in dailyData
            dailyData[currentDay + 1] = DailyDataStore({
                dayShareRate: g._shareRate
            });

            // 2) Apply next day's rate adjustments
            _applyDailyRate(g);
        }

        // Update dailyDataCount to the day we processed up to
        g._dailyDataCount = endDayForLoop;

        // Emit a single event showing the final shareRate and day processed up to
        // Note: The event timestamp is current block, but day processed might be in the past
        emit DailyDataUpdate(block.timestamp, g._dailyDataCount, g._shareRate);

        // Return true only if we reached the target day
        return g._dailyDataCount == beforeDay;
    }

    /**
     * @dev Calculates and applies the share rate growth for a single day. The growth is determined by
     *      a ratio derived from the current token value of all locked shares relative to the conceptual total supply.
     */
    function _applyDailyRate(GlobalsCache memory g) internal view {
        // Get locked ratio in 1e8 scale
        uint256 ratio = _getLockedRatio(g);
        
        // If ratio is 0 (returned by _getLockedRatio if currentValueOfLockedShares is 0),
        // the share rate should not increase. It remains unchanged for this day.
        if (ratio == 0) {
            return; // No change to g._shareRate
        }
        
        // If ratio is non-zero, _getLockedRatio ensures it's at least 1e5 (0.1%).
        // Calculate target annual APR (in 1e8 scale)
        // 3.69% when ratio is 100%, 36.9% when ratio is 10%, 369% when ratio is 1%.
        // Min ratio of 1e5 (0.1%) means max APR from this formula is 36900%.
        uint256 targetAnnualPercent = (3690000 * 1e8) / ratio;
        
        // Calculate daily rate that will compound to the annual multiplier
        // Formula: dailyRate = (annualMultiplier)^(1/365) - 1

        // Note on Approximations, Achieved APY for Savers, and Total Supply Inflation:
        //
        // 1. Approximation Precision and Achieved APY for Savers:
        // The daily rate calculation uses approximations. These may become less precise at very
        // high targetAnnualPercent values (which occur when the locked token ratio is low).
        // In such scenarios, the approximation tends to be conservative (i.e., it underestimates
        // the required daily rate), potentially resulting in an actual compounded APY for savers
        // that is lower than the high targetAnnualPercent. This means that while a lower locked
        // ratio aims for a higher *targeted APY for savers*, the actually achieved APY might be
        // tempered by this approximation. Conversely, for lower targetAnnualPercent values
        // (e.g., at high locked ratios), the deviation between the actual APY for savers and
        // the targetAnnualPercent might differ (often being slightly higher due to the nature of the approximation).
        //
        // 2. Impact on Total Supply Inflation:
        // This interplay affects the overall total supply inflation (i.e., the total number of new tokens minted).
        // Extremely low locked ratios (e.g., <1%) target very high APYs for savers. However, the
        // actually achieved APY on this small base of locked tokens (potentially reduced further by the
        // approximation's conservatism at high targets) can result in fewer absolute new tokens minted
        // compared to scenarios with higher locked ratios.
        // Higher locked ratios (e.g., 10%-100%) apply a more moderate (and more accurately achieved) APY
        // for savers to a larger base of locked tokens. Consequently, the total supply inflation
        // (measured as new tokens minted as a percentage of the conceptual total supply) may actually
        // be higher when more tokens are locked. This total supply inflation might not significantly
        // exceed the ~3.69% annual rate (which is achieved when 100% of tokens are locked and earn
        // a ~3.69% APY for savers), even if APY targets for savers are drastically higher at very
        // low locked ratios.

        // Conservative approximation that works for our range:
        uint256 dailyRate;
        if (targetAnnualPercent <= 10e8) {  // <= 1000%
            // Use logarithmic approximation: ln(1+x) ≈ x/(1+x/2)
            dailyRate = (targetAnnualPercent * 1e8) / (1e8 + targetAnnualPercent / 2) / 365;
        } else {
            // For very high rates, use direct calculation with better approximation
            // This gives a more accurate daily rate for high annual percentages
            dailyRate = (targetAnnualPercent * 1e8) / (1e8 + targetAnnualPercent / 3) / 365;
        }
        
        // Apply the daily rate to the share rate
        g._shareRate = (g._shareRate * (1e8 + dailyRate)) / 1e8;
    }

    // ------------------------------------------------------------------------
    // Ratio / Rate Calculation Helpers
    // ------------------------------------------------------------------------

    /**
     * @dev Calculates the ratio of the current token value of all locked shares
     *      to the conceptual total supply (circulating supply + current token value of locked shares).
     *      The ratio is scaled by 1e8 (e.g., 1e8 represents a 1.0 ratio, 1e7 represents 0.1).
     */
    function _getLockedRatio(GlobalsCache memory g) internal view returns (uint256) {
        // Use totalSupply() from ERC20 (which is circulating supply)
        uint256 circulatingSupply = totalSupply();

        // Calculate the current "value" of all locked shares in terms of tokens
        uint256 currentValueOfLockedShares;
        if (g._totalSharesLocked == 0 || g._shareRate == 0) { // Or if shareRate is 0 to prevent division by zero issues with shares.
            currentValueOfLockedShares = 0;
        } else {
            // Note: _shareRate is scaled by 1e8. _totalSharesLocked is the sum of shares.
            // If shareRate was 1e8 initially, shares = tokens.
            // So, value = (shares * currentShareRate) / 1e8 is correct.
            currentValueOfLockedShares = (g._totalSharesLocked * g._shareRate) / 1e8;
        }

        uint256 definedConceptualTotal = circulatingSupply + currentValueOfLockedShares;

        if (definedConceptualTotal == 0) {
            // If the conceptual total supply is zero (no circulating supply and no value in locked shares), the ratio is 0.
            // This results in no share rate increase for the day when processed by _applyDailyRate.
            return 0;
        }
        
        if (currentValueOfLockedShares == 0) {
            // If there's a conceptual total supply but no value is currently locked (e.g., all shares have zero value or no shares exist), the ratio is 0.
            // This also results in no share rate increase for the day via _applyDailyRate.
            return 0;
        }

        // Calculate ratio: (currentValueOfLockedShares * 1e8) / definedConceptualTotal
        uint256 ratio = (currentValueOfLockedShares * 1e8) / definedConceptualTotal;
        
        // Enforce minimum ratio of 0.1% to prevent excessive share rate growth if actual ratio is extremely low.
        // 1e5 in 1e8 scale equals 0.1%.
        if (ratio < 1e5) {
            return 1e5;
        }
        
        return ratio;
    }

    // ------------------------------------------------------------------------
    // Saving Feature (Single 369-day lock)
    // ------------------------------------------------------------------------

    /**
     * @dev Start a new saving by burning the specified number of tokens. 
     *      The user receives "shares" at the current shareRate. 
     *      The saving can only be ended after the fixed 369-day period.
     */
    function saveStart(uint256 newSavedTokens) external {
        require(newSavedTokens > 0, "Oase: Must save more than 0");
        require(newSavedTokens >= 1 * 10**8, "Oase: Minimum save is 1 token");
        require(newSavedTokens < 2**72, "Oase: Amount too large for event packing");
        // use balanceOf from ERC20
        require(balanceOf(msg.sender) >= newSavedTokens, "Oase: insufficient balance");
        require(savingsLists[msg.sender].length < 100, "Oase: User has reached the maximum number of savings");

        // Load globals and take snapshot
        GlobalsCache memory g;
        GlobalsCache memory gSnapshot;
        _globalsLoad(g, gSnapshot);

        // Update daily data, require it to be complete
        bool updated = _dailyDataUpdate(g, _currentDay());
        require(updated, "Oase: Daily data update incomplete, call updateDailyData()");

        // Increment saving ID
        uint40 newSavingId = ++latestSavingId;

        // Calculate shares at the current shareRate FIRST
        // shares = tokens * 1e8 / shareRate
        uint256 newSavingShares = (newSavedTokens * 1e8) / g._shareRate;
        require(newSavingShares > 0, "Oase: Amount too small for current share rate");
        require(newSavingShares < 2**72, "Oase: Shares too large for event packing");

        // Now that we know the shares are valid, burn the tokens
        // Use inherited _burn instead of custom storage
        _burn(msg.sender, newSavedTokens);

        // savingStartDay is next day
        uint256 newSavingStartDay = g._currentDay + 1;
        // savingEndDay is + 369 from start
        uint256 newSavingEndDay = newSavingStartDay + LOCK_LENGTH_DAYS;

        // Create new saving entry
        savingsLists[msg.sender].push(SavingStore({
            savingId: newSavingId,
            savedShares: newSavingShares,
            savedTokens: newSavedTokens,
            savingStartDay: newSavingStartDay,
            savingEndDay: newSavingEndDay
        }));

        // Increase locked supply (original tokens) and total shares locked
        g._lockedTokenSupply += newSavedTokens; // Track initial tokens locked
        g._totalSharesLocked += newSavingShares; // Track total shares

        // Log 
        emit SavingStart(
            uint256(uint40(block.timestamp))
                | (uint256(uint72(newSavedTokens)) << 40)
                | (uint256(uint72(newSavingShares)) << 112),
            msg.sender,
            newSavingId
        );

        // Sync changes back to storage
        _globalsSync(g, gSnapshot);
    }

    /**
     * @dev End a saving that has completed its 369-day lock. 
     *      We compute the final payout by converting shares to tokens at the *current* shareRate.
     *      We also auto-update daily if needed. 
     */
    function saveEnd(uint256 saveIndex, uint40 saveIdParam) external {
        // Load globals and take snapshot
        GlobalsCache memory g;
        GlobalsCache memory gSnapshot;
        _globalsLoad(g, gSnapshot);

        // Update daily data, require it to be complete
        uint256 currentDay = _currentDay();
        bool updated = _dailyDataUpdate(g, currentDay);
        require(updated, "Oase: Daily data update incomplete, call updateDailyData()");

        SavingStore[] storage userSavings = savingsLists[msg.sender];
        require(userSavings.length != 0, "Oase: no savings");
        require(saveIndex < userSavings.length, "Oase: invalid index");

        SavingStore storage stRef = userSavings[saveIndex];
        require(stRef.savingId == saveIdParam, "Oase: savingId mismatch");

        // Ensure 369 days have passed from savingEndDay
        require(g._currentDay >= stRef.savingEndDay, "Oase: saving not matured");

        // Calculate payout
        // payout = shares * shareRate / 1e8
        uint256 payoutTokens = (stRef.savedShares * g._shareRate) / 1e8;

        // Emit the event now, including the number of shares & final payout
        emit SavingEnd(
            uint256(uint40(block.timestamp))
                | (uint256(uint72(stRef.savedShares)) << 40)
                | (uint256(uint72(payoutTokens)) << 112),
            msg.sender,
            stRef.savingId
        );

        // Remove from locked supply
        g._lockedTokenSupply -= stRef.savedTokens;
        g._totalSharesLocked -= stRef.savedShares;

        // Remove saving from array by swapping with last & popping
        _removeSaving(userSavings, saveIndex);

        // Sync
        _globalsSync(g, gSnapshot);

        // Mint final tokens to user 
        _mint(msg.sender, payoutTokens);
    }

    // ------------------------------------------------------------------------
    // Internal Helpers
    // ------------------------------------------------------------------------

    /**
     * @dev Public function to allow anyone to advance the daily data updates.
     *      Processes up to MAX_DAYS_TO_UPDATE days per call.
     *      Call this multiple times if needed to catch up a large backlog.
     */
    function updateDailyData() external {
        GlobalsCache memory g;
        GlobalsCache memory gSnapshot;
        _globalsLoad(g, gSnapshot);

        // Call _dailyDataUpdate but ignore the return value.
        // We just want to process a batch if there's a backlog.
        _dailyDataUpdate(g, _currentDay());

        _globalsSync(g, gSnapshot);
    }

    /**
     * @dev Remove an element from an array by swapping with the last and popping.
     */
    function _removeSaving(SavingStore[] storage arr, uint256 index) internal {
        uint256 lastIndex = arr.length - 1;
        if (index != lastIndex) {
            arr[index] = arr[lastIndex];
        }
        arr.pop();
    }

    /**
     * @dev Returns all of a user's SavingStore entries in a single array.
     */
    function getUserSavings(address user) external view returns (SavingStore[] memory) {
        return savingsLists[user];
    }

    /**
     * @dev Allows users to claim tokens if they are in the merkle tree.
     * @param id Unique identifier for the claim
     * @param amount Amount of tokens to claim
     * @param mintingStartDate Start date of the claim period
     * @param mintingEndDate End date of the claim period
     * @param merkleProof Merkle proof verifying the claim
     * @param signature Optional signature if claiming on behalf of someone else
     */
    function claim(
        uint256 id,
        uint256 amount,
        uint256 mintingStartDate,
        uint256 mintingEndDate,
        bytes32[] calldata merkleProof,
        bytes calldata signature
    ) external {
        require(block.timestamp >= mintingStartDate && block.timestamp <= mintingEndDate, "Not within the minting window.");
        require(!hasClaimedId[id], "This ID has already been claimed.");

        address eligibleAddress;

        if (signature.length > 0) {
            // Case with signature: Recover the eligible address from the signature
            // Include all relevant parameters to prevent replay attacks across contracts, chains, and time windows
            bytes32 messageHash = keccak256(abi.encode(
                msg.sender, 
                id, 
                amount, 
                mintingStartDate,
                mintingEndDate,
                block.chainid,
                address(this)
            ));
            bytes32 ethSignedMessageHash = MessageHashUtils.toEthSignedMessageHash(messageHash);
            eligibleAddress = ECDSA.recover(ethSignedMessageHash, signature);
        } else {
            eligibleAddress = msg.sender;
        }

        // Construct the node from the eligible address
        bytes32 node = keccak256(abi.encode(id, eligibleAddress, amount, mintingStartDate, mintingEndDate));

        // Verify the provided Merkle proof against the stored Merkle root
        require(MerkleProof.verify(merkleProof, merkleRoot, node), "Invalid Merkle proof.");

        // Record the claim and mint the tokens
        hasClaimedId[id] = true;
        _mint(msg.sender, amount);

        emit Claimed(id, msg.sender, amount, block.timestamp);
    }
}
        

Contract ABI

[{"type":"constructor","stateMutability":"nonpayable","inputs":[{"type":"bytes32","name":"_merkleRoot","internalType":"bytes32"}]},{"type":"error","name":"ECDSAInvalidSignature","inputs":[]},{"type":"error","name":"ECDSAInvalidSignatureLength","inputs":[{"type":"uint256","name":"length","internalType":"uint256"}]},{"type":"error","name":"ECDSAInvalidSignatureS","inputs":[{"type":"bytes32","name":"s","internalType":"bytes32"}]},{"type":"event","name":"Approval","inputs":[{"type":"address","name":"owner","internalType":"address","indexed":true},{"type":"address","name":"spender","internalType":"address","indexed":true},{"type":"uint256","name":"value","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"Claimed","inputs":[{"type":"uint256","name":"id","internalType":"uint256","indexed":true},{"type":"address","name":"account","internalType":"address","indexed":true},{"type":"uint256","name":"amount","internalType":"uint256","indexed":false},{"type":"uint256","name":"timestamp","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"DailyDataUpdate","inputs":[{"type":"uint256","name":"timestamp","internalType":"uint256","indexed":true},{"type":"uint256","name":"currentDay","internalType":"uint256","indexed":true},{"type":"uint256","name":"newShareRate","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"SavingEnd","inputs":[{"type":"uint256","name":"data","internalType":"uint256","indexed":true},{"type":"address","name":"saverAddr","internalType":"address","indexed":true},{"type":"uint40","name":"savingId","internalType":"uint40","indexed":true}],"anonymous":false},{"type":"event","name":"SavingStart","inputs":[{"type":"uint256","name":"data","internalType":"uint256","indexed":true},{"type":"address","name":"saverAddr","internalType":"address","indexed":true},{"type":"uint40","name":"savingId","internalType":"uint40","indexed":true}],"anonymous":false},{"type":"event","name":"Transfer","inputs":[{"type":"address","name":"from","internalType":"address","indexed":true},{"type":"address","name":"to","internalType":"address","indexed":true},{"type":"uint256","name":"value","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"LAUNCH_TIME","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"LOCK_LENGTH_DAYS","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"allowance","inputs":[{"type":"address","name":"owner","internalType":"address"},{"type":"address","name":"spender","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"approve","inputs":[{"type":"address","name":"spender","internalType":"address"},{"type":"uint256","name":"amount","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"balanceOf","inputs":[{"type":"address","name":"account","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"claim","inputs":[{"type":"uint256","name":"id","internalType":"uint256"},{"type":"uint256","name":"amount","internalType":"uint256"},{"type":"uint256","name":"mintingStartDate","internalType":"uint256"},{"type":"uint256","name":"mintingEndDate","internalType":"uint256"},{"type":"bytes32[]","name":"merkleProof","internalType":"bytes32[]"},{"type":"bytes","name":"signature","internalType":"bytes"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"dayShareRate","internalType":"uint256"}],"name":"dailyData","inputs":[{"type":"uint256","name":"","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint8","name":"","internalType":"uint8"}],"name":"decimals","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"decreaseAllowance","inputs":[{"type":"address","name":"spender","internalType":"address"},{"type":"uint256","name":"subtractedValue","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"tuple[]","name":"","internalType":"struct Oase.SavingStore[]","components":[{"type":"uint40","name":"savingId","internalType":"uint40"},{"type":"uint256","name":"savedShares","internalType":"uint256"},{"type":"uint256","name":"savedTokens","internalType":"uint256"},{"type":"uint256","name":"savingStartDay","internalType":"uint256"},{"type":"uint256","name":"savingEndDay","internalType":"uint256"}]}],"name":"getUserSavings","inputs":[{"type":"address","name":"user","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"shareRate","internalType":"uint256"},{"type":"uint256","name":"dailyDataCount","internalType":"uint256"},{"type":"uint256","name":"lockedTokenSupply","internalType":"uint256"},{"type":"uint256","name":"totalSharesLocked","internalType":"uint256"}],"name":"globals","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"hasClaimedId","inputs":[{"type":"uint256","name":"","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"increaseAllowance","inputs":[{"type":"address","name":"spender","internalType":"address"},{"type":"uint256","name":"addedValue","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint40","name":"","internalType":"uint40"}],"name":"latestSavingId","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"merkleRoot","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"string","name":"","internalType":"string"}],"name":"name","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"saveEnd","inputs":[{"type":"uint256","name":"saveIndex","internalType":"uint256"},{"type":"uint40","name":"saveIdParam","internalType":"uint40"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"saveStart","inputs":[{"type":"uint256","name":"newSavedTokens","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint40","name":"savingId","internalType":"uint40"},{"type":"uint256","name":"savedShares","internalType":"uint256"},{"type":"uint256","name":"savedTokens","internalType":"uint256"},{"type":"uint256","name":"savingStartDay","internalType":"uint256"},{"type":"uint256","name":"savingEndDay","internalType":"uint256"}],"name":"savingsLists","inputs":[{"type":"address","name":"","internalType":"address"},{"type":"uint256","name":"","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"string","name":"","internalType":"string"}],"name":"symbol","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"totalSupply","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"transfer","inputs":[{"type":"address","name":"to","internalType":"address"},{"type":"uint256","name":"amount","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"transferFrom","inputs":[{"type":"address","name":"from","internalType":"address"},{"type":"address","name":"to","internalType":"address"},{"type":"uint256","name":"amount","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"updateDailyData","inputs":[]}]
              

Contract Creation Code

Verify & Publish
0x60c060405234801562000010575f80fd5b5060405162002493380380620024938339810160408190526200003391620000b3565b604051806040016040528060048152602001635377617960e01b815250604051806040016040528060048152602001635357415960e01b81525081600390816200007e91906200016b565b5060046200008d82826200016b565b50505060a052426080526305f5e1006005555f6006819055600781905560085562000233565b5f60208284031215620000c4575f80fd5b5051919050565b634e487b7160e01b5f52604160045260245ffd5b600181811c90821680620000f457607f821691505b6020821081036200011357634e487b7160e01b5f52602260045260245ffd5b50919050565b601f82111562000166575f81815260208120601f850160051c81016020861015620001415750805b601f850160051c820191505b8181101562000162578281556001016200014d565b5050505b505050565b81516001600160401b03811115620001875762000187620000cb565b6200019f81620001988454620000df565b8462000119565b602080601f831160018114620001d5575f8415620001bd5750858301515b5f19600386901b1c1916600185901b17855562000162565b5f85815260208120601f198616915b828110156200020557888601518255948401946001909101908401620001e4565b50858210156200022357878501515f19600388901b60f8161c191681555b5050505050600190811b01905550565b60805160a051612230620002635f395f81816101e901526106f501525f818161015c01526115e601526122305ff3fe608060405234801561000f575f80fd5b5060043610610153575f3560e01c806390de6871116100bf578063db54576f11610079578063db54576f14610335578063dd62ed3e1461033d578063dfd8bf6814610350578063f80a205714610359578063f8f8b5a314610379578063fa0ff5a0146103bc575f80fd5b806390de68711461029057806395d89b41146102af578063a457c2d7146102b7578063a9059cbb146102ca578063c3124525146102dd578063cfd049d514610313575f80fd5b8063313ce56711610110578063313ce5671461020b578063395093511461021a5780635bb58d661461022d578063662a025e1461024257806370a08231146102555780638b4729d71461027d575f80fd5b8063022466b51461015757806306fdde0314610191578063095ea7b3146101a657806318160ddd146101c957806323b872dd146101d15780632eb4a7ab146101e4575b5f80fd5b61017e7f000000000000000000000000000000000000000000000000000000000000000081565b6040519081526020015b60405180910390f35b6101996103e3565b6040516101889190611da9565b6101b96101b4366004611e0f565b610473565b6040519015158152602001610188565b60025461017e565b6101b96101df366004611e37565b61048c565b61017e7f000000000000000000000000000000000000000000000000000000000000000081565b60405160088152602001610188565b6101b9610228366004611e0f565b6104af565b61024061023b366004611eb5565b6104d0565b005b610240610250366004611f72565b6107ce565b61017e610263366004611f89565b6001600160a01b03165f9081526020819052604090205490565b61024061028b366004611fa2565b610cb2565b61017e61029e366004611f72565b60096020525f908152604090205481565b610199610f5f565b6101b96102c5366004611e0f565b610f6e565b6101b96102d8366004611e0f565b610fe8565b6005546006546007546008546102f39392919084565b604080519485526020850193909352918301526060820152608001610188565b6101b9610321366004611f72565b600c6020525f908152604090205460ff1681565b610240610ff5565b61017e61034b366004611fd9565b61102a565b61017e61017181565b61036c610367366004611f89565b611054565b604051610188919061200a565b61038c610387366004611e0f565b6110fa565b6040805164ffffffffff90961686526020860194909452928401919091526060830152608082015260a001610188565b600a546103cd9064ffffffffff1681565b60405164ffffffffff9091168152602001610188565b6060600380546103f29061207e565b80601f016020809104026020016040519081016040528092919081815260200182805461041e9061207e565b80156104695780601f1061044057610100808354040283529160200191610469565b820191905f5260205f20905b81548152906001019060200180831161044c57829003601f168201915b5050505050905090565b5f3361048081858561114c565b60019150505b92915050565b5f33610499858285611270565b6104a48585856112e8565b506001949350505050565b5f336104808185856104c1838361102a565b6104cb91906120ca565b61114c565b8542101580156104e05750844211155b6105315760405162461bcd60e51b815260206004820152601e60248201527f4e6f742077697468696e20746865206d696e74696e672077696e646f772e000060448201526064015b60405180910390fd5b5f888152600c602052604090205460ff16156105995760405162461bcd60e51b815260206004820152602160248201527f546869732049442068617320616c7265616479206265656e20636c61696d65646044820152601760f91b6064820152608401610528565b5f811561067057604080513360208201529081018a9052606081018990526080810188905260a081018790524660c08201523060e08201525f90610100016040516020818303038152906040528051906020012090505f610626827f19457468657265756d205369676e6564204d6573736167653a0a3332000000005f908152601c91909152603c902090565b90506106678186868080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061148a92505050565b92505050610673565b50335b60408051602081018b90526001600160a01b03831691810191909152606081018990526080810188905260a081018790525f9060c0016040516020818303038152906040528051906020012090506107208686808060200260200160405190810160405280939291908181526020018383602002808284375f920191909152507f000000000000000000000000000000000000000000000000000000000000000092508591506114b29050565b6107645760405162461bcd60e51b815260206004820152601560248201527424b73b30b634b21026b2b935b63290383937b7b31760591b6044820152606401610528565b5f8a8152600c60205260409020805460ff19166001179055610786338a6114c7565b604080518a815242602082015233918c917fd9cb1e2714d65a111c0f20f060176ad657496bd47a3de04ec7c3d4ca232112ac910160405180910390a350505050505050505050565b5f811161081d5760405162461bcd60e51b815260206004820152601b60248201527f4f6173653a204d7573742073617665206d6f7265207468616e203000000000006044820152606401610528565b6305f5e1008110156108715760405162461bcd60e51b815260206004820152601d60248201527f4f6173653a204d696e696d756d2073617665206973203120746f6b656e0000006044820152606401610528565b600160481b81106108d55760405162461bcd60e51b815260206004820152602860248201527f4f6173653a20416d6f756e7420746f6f206c6172676520666f72206576656e74604482015267207061636b696e6760c01b6064820152608401610528565b335f908152602081905260409020548111156109335760405162461bcd60e51b815260206004820152601a60248201527f4f6173653a20696e73756666696369656e742062616c616e63650000000000006044820152606401610528565b335f908152600b60205260409020546064116109ae5760405162461bcd60e51b815260206004820152603460248201527f4f6173653a205573657220686173207265616368656420746865206d6178696d604482015273756d206e756d626572206f6620736176696e677360601b6064820152608401610528565b6109b6611d7f565b6109be611d7f565b6109c88282611584565b5f6109da836109d56115dc565b61161a565b9050806109f95760405162461bcd60e51b8152600401610528906120dd565b600a80545f91908290610a129064ffffffffff1661213a565b91906101000a81548164ffffffffff021916908364ffffffffff160217905590505f845f0151866305f5e100610a48919061215d565b610a529190612174565b90505f8111610ab95760405162461bcd60e51b815260206004820152602d60248201527f4f6173653a20416d6f756e7420746f6f20736d616c6c20666f7220637572726560448201526c6e74207368617265207261746560981b6064820152608401610528565b600160481b8110610b1d5760405162461bcd60e51b815260206004820152602860248201527f4f6173653a2053686172657320746f6f206c6172676520666f72206576656e74604482015267207061636b696e6760c01b6064820152608401610528565b610b273387611721565b5f85608001516001610b3991906120ca565b90505f610b48610171836120ca565b9050600b5f336001600160a01b03166001600160a01b031681526020019081526020015f206040518060a001604052808664ffffffffff1681526020018581526020018a815260200184815260200183815250908060018154018082558091505060019003905f5260205f2090600502015f909190919091505f820151815f015f6101000a81548164ffffffffff021916908364ffffffffff1602179055506020820151816001015560408201518160020155606082015181600301556080820151816004015550508787604001818151610c2391906120ca565b905250606087018051849190610c3a9083906120ca565b90525060405164ffffffffff80861691339142166dffffffffffffffffff000000000060288d901b161768ffffffffffffffffff60701b607088901b1617907f390cc673cefaad1ca89a4ddf40d5a3199e7ffab1ad3d1c408fa22e7e473102a7905f90a4610ca88787611849565b5050505050505050565b610cba611d7f565b610cc2611d7f565b610ccc8282611584565b5f610cd56115dc565b90505f610ce2848361161a565b905080610d015760405162461bcd60e51b8152600401610528906120dd565b335f908152600b602052604081208054909103610d535760405162461bcd60e51b815260206004820152601060248201526f4f6173653a206e6f20736176696e677360801b6044820152606401610528565b80548710610d995760405162461bcd60e51b815260206004820152601360248201527209ec2e6ca7440d2dcecc2d8d2c840d2dcc8caf606b1b6044820152606401610528565b5f818881548110610dac57610dac612193565b5f9182526020909120600590910201805490915064ffffffffff888116911614610e185760405162461bcd60e51b815260206004820152601760248201527f4f6173653a20736176696e674964206d69736d617463680000000000000000006044820152606401610528565b806004015486608001511015610e705760405162461bcd60e51b815260206004820152601860248201527f4f6173653a20736176696e67206e6f74206d61747572656400000000000000006044820152606401610528565b5f6305f5e100875f01518360010154610e89919061215d565b610e939190612174565b8254600184015460405192935064ffffffffff918216923392607086901b68ffffffffffffffffff60701b1660289390931b6dffffffffffffffffff000000000016429091161791909117907ff0019f5bb966673bc3d326e7d459457783cb80b7edb41cf11164fbc23e9d2f93905f90a4816002015487604001818151610f1a91906121a7565b9052506001820154606088018051610f339083906121a7565b905250610f40838a6118ac565b610f4a8787611849565b610f5433826114c7565b505050505050505050565b6060600480546103f29061207e565b5f3381610f7b828661102a565b905083811015610fdb5760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152608401610528565b6104a4828686840361114c565b5f336104808185856112e8565b610ffd611d7f565b611005611d7f565b61100f8282611584565b61101b826109d56115dc565b506110268282611849565b5050565b6001600160a01b039182165f90815260016020908152604080832093909416825291909152205490565b6001600160a01b0381165f908152600b60209081526040808320805482518185028101850190935280835260609492939192909184015b828210156110ef575f8481526020908190206040805160a08101825260058602909201805464ffffffffff1683526001808201548486015260028201549284019290925260038101546060840152600401546080830152908352909201910161108b565b505050509050919050565b600b602052815f5260405f208181548110611113575f80fd5b5f9182526020909120600590910201805460018201546002830154600384015460049094015464ffffffffff9093169550909350919085565b6001600160a01b0383166111ae5760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610528565b6001600160a01b03821661120f5760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610528565b6001600160a01b038381165f8181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b5f61127b848461102a565b90505f1981146112e257818110156112d55760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610528565b6112e2848484840361114c565b50505050565b6001600160a01b03831661134c5760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610528565b6001600160a01b0382166113ae5760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610528565b6001600160a01b0383165f90815260208190526040902054818110156114255760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610528565b6001600160a01b038481165f81815260208181526040808320878703905593871680835291849020805487019055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a36112e2565b5f805f806114988686611996565b9250925092506114a882826119df565b5090949350505050565b5f826114be8584611a97565b14949350505050565b6001600160a01b03821661151d5760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610528565b8060025f82825461152e91906120ca565b90915550506001600160a01b0382165f81815260208181526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b60055482526006546020830152600754604083015260085460608301526115a96115dc565b60808381019182528351835260208085015190840152604080850151908401526060938401519383019390935251910152565b5f6201518061160b7f0000000000000000000000000000000000000000000000000000000000000000426121a7565b6116159190612174565b905090565b5f808360200151831161162d575f61163c565b602084015161163c90846121a7565b9050805f0361164f576001915050610486565b5f610171821161165f5781611663565b6101715b90505f81866020015161167691906120ca565b60208701519091505b818110156116d25760408051602081019091528751815260095f6116a48460016120ca565b815260208101919091526040015f20905190556116c087611ae3565b806116ca816121ba565b91505061167f565b5060208681018290528651604051908152829142917f8caa10406f5fa75d56a3b35f15b317829cd60a331e7048de76e042d088203bfb910160405180910390a350505060209092015114919050565b6001600160a01b0382166117815760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b6064820152608401610528565b6001600160a01b0382165f90815260208190526040902054818110156117f45760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b6064820152608401610528565b6001600160a01b0383165f818152602081815260408083208686039055600280548790039055518581529192917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9101611263565b8051825114158061186257508060200151826020015114155b8061187557508060400151826040015114155b8061188857508060600151826060015114155b15611026575080516005556020810151600655604081015160075560600151600855565b81545f906118bc906001906121a7565b9050808214611947578281815481106118d7576118d7612193565b905f5260205f2090600502018383815481106118f5576118f5612193565b5f91825260209091208254600590920201805464ffffffffff191664ffffffffff9092169190911781556001808301549082015560028083015490820155600380830154908201556004918201549101555b82805480611957576119576121d2565b5f8281526020812060055f1990930192830201805464ffffffffff19168155600181018290556002810182905560038101829055600401559055505050565b5f805f83516041036119cd576020840151604085015160608601515f1a6119bf88828585611bcd565b9550955095505050506119d8565b505081515f91506002905b9250925092565b5f8260038111156119f2576119f26121e6565b036119fb575050565b6001826003811115611a0f57611a0f6121e6565b03611a2d5760405163f645eedf60e01b815260040160405180910390fd5b6002826003811115611a4157611a416121e6565b03611a625760405163fce698f760e01b815260048101829052602401610528565b6003826003811115611a7657611a766121e6565b03611026576040516335e2f38360e21b815260048101829052602401610528565b5f81815b8451811015611adb57611ac782868381518110611aba57611aba612193565b6020026020010151611c95565b915080611ad3816121ba565b915050611a9b565b509392505050565b5f611aed82611cc4565b9050805f03611afa575050565b5f611b0c8266014f9a81ec1000612174565b90505f633b9aca008211611b5f5761016d611b28600284612174565b611b36906305f5e1006120ca565b611b44846305f5e10061215d565b611b4e9190612174565b611b589190612174565b9050611ba0565b61016d611b6d600384612174565b611b7b906305f5e1006120ca565b611b89846305f5e10061215d565b611b939190612174565b611b9d9190612174565b90505b6305f5e100611baf82826120ca565b8551611bbb919061215d565b611bc59190612174565b909352505050565b5f80807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841115611c0657505f91506003905082611c8b565b604080515f808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa158015611c57573d5f803e3d5ffd5b5050604051601f1901519150506001600160a01b038116611c8257505f925060019150829050611c8b565b92505f91508190505b9450945094915050565b5f818310611caf575f828152602084905260409020611cbd565b5f8381526020839052604090205b9392505050565b5f80611ccf60025490565b90505f83606001515f1480611ce357508351155b15611cef57505f611d12565b835160608501516305f5e10091611d059161215d565b611d0f9190612174565b90505b5f611d1d82846120ca565b9050805f03611d3057505f949350505050565b815f03611d4157505f949350505050565b5f81611d51846305f5e10061215d565b611d5b9190612174565b9050620186a0811015611d765750620186a095945050505050565b95945050505050565b6040518060a001604052805f81526020015f81526020015f81526020015f81526020015f81525090565b5f6020808352835180828501525f5b81811015611dd457858101830151858201604001528201611db8565b505f604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b0381168114611e0a575f80fd5b919050565b5f8060408385031215611e20575f80fd5b611e2983611df4565b946020939093013593505050565b5f805f60608486031215611e49575f80fd5b611e5284611df4565b9250611e6060208501611df4565b9150604084013590509250925092565b5f8083601f840112611e80575f80fd5b50813567ffffffffffffffff811115611e97575f80fd5b602083019150836020828501011115611eae575f80fd5b9250929050565b5f805f805f805f8060c0898b031215611ecc575f80fd5b88359750602089013596506040890135955060608901359450608089013567ffffffffffffffff80821115611eff575f80fd5b818b0191508b601f830112611f12575f80fd5b813581811115611f20575f80fd5b8c60208260051b8501011115611f34575f80fd5b6020830196508095505060a08b0135915080821115611f51575f80fd5b50611f5e8b828c01611e70565b999c989b5096995094979396929594505050565b5f60208284031215611f82575f80fd5b5035919050565b5f60208284031215611f99575f80fd5b611cbd82611df4565b5f8060408385031215611fb3575f80fd5b82359150602083013564ffffffffff81168114611fce575f80fd5b809150509250929050565b5f8060408385031215611fea575f80fd5b611ff383611df4565b915061200160208401611df4565b90509250929050565b602080825282518282018190525f919060409081850190868401855b82811015612071578151805164ffffffffff16855286810151878601528581015186860152606080820151908601526080908101519085015260a09093019290850190600101612026565b5091979650505050505050565b600181811c9082168061209257607f821691505b6020821081036120b057634e487b7160e01b5f52602260045260245ffd5b50919050565b634e487b7160e01b5f52601160045260245ffd5b80820180821115610486576104866120b6565b6020808252603a908201527f4f6173653a204461696c7920646174612075706461746520696e636f6d706c6560408201527f74652c2063616c6c207570646174654461696c79446174612829000000000000606082015260800190565b5f64ffffffffff808316818103612153576121536120b6565b6001019392505050565b8082028115828204841417610486576104866120b6565b5f8261218e57634e487b7160e01b5f52601260045260245ffd5b500490565b634e487b7160e01b5f52603260045260245ffd5b81810381811115610486576104866120b6565b5f600182016121cb576121cb6120b6565b5060010190565b634e487b7160e01b5f52603160045260245ffd5b634e487b7160e01b5f52602160045260245ffdfea2646970667358221220111f3bb3d96f253e4b108a2bb38a7cf98633167ff954618312c447ede28f1dbc64736f6c6343000814003345711452762d9dab167c16cc1f8672d6b3102251471bbf07113c1e1134d39eea

Deployed ByteCode

0x608060405234801561000f575f80fd5b5060043610610153575f3560e01c806390de6871116100bf578063db54576f11610079578063db54576f14610335578063dd62ed3e1461033d578063dfd8bf6814610350578063f80a205714610359578063f8f8b5a314610379578063fa0ff5a0146103bc575f80fd5b806390de68711461029057806395d89b41146102af578063a457c2d7146102b7578063a9059cbb146102ca578063c3124525146102dd578063cfd049d514610313575f80fd5b8063313ce56711610110578063313ce5671461020b578063395093511461021a5780635bb58d661461022d578063662a025e1461024257806370a08231146102555780638b4729d71461027d575f80fd5b8063022466b51461015757806306fdde0314610191578063095ea7b3146101a657806318160ddd146101c957806323b872dd146101d15780632eb4a7ab146101e4575b5f80fd5b61017e7f0000000000000000000000000000000000000000000000000000000068e4ce2f81565b6040519081526020015b60405180910390f35b6101996103e3565b6040516101889190611da9565b6101b96101b4366004611e0f565b610473565b6040519015158152602001610188565b60025461017e565b6101b96101df366004611e37565b61048c565b61017e7f45711452762d9dab167c16cc1f8672d6b3102251471bbf07113c1e1134d39eea81565b60405160088152602001610188565b6101b9610228366004611e0f565b6104af565b61024061023b366004611eb5565b6104d0565b005b610240610250366004611f72565b6107ce565b61017e610263366004611f89565b6001600160a01b03165f9081526020819052604090205490565b61024061028b366004611fa2565b610cb2565b61017e61029e366004611f72565b60096020525f908152604090205481565b610199610f5f565b6101b96102c5366004611e0f565b610f6e565b6101b96102d8366004611e0f565b610fe8565b6005546006546007546008546102f39392919084565b604080519485526020850193909352918301526060820152608001610188565b6101b9610321366004611f72565b600c6020525f908152604090205460ff1681565b610240610ff5565b61017e61034b366004611fd9565b61102a565b61017e61017181565b61036c610367366004611f89565b611054565b604051610188919061200a565b61038c610387366004611e0f565b6110fa565b6040805164ffffffffff90961686526020860194909452928401919091526060830152608082015260a001610188565b600a546103cd9064ffffffffff1681565b60405164ffffffffff9091168152602001610188565b6060600380546103f29061207e565b80601f016020809104026020016040519081016040528092919081815260200182805461041e9061207e565b80156104695780601f1061044057610100808354040283529160200191610469565b820191905f5260205f20905b81548152906001019060200180831161044c57829003601f168201915b5050505050905090565b5f3361048081858561114c565b60019150505b92915050565b5f33610499858285611270565b6104a48585856112e8565b506001949350505050565b5f336104808185856104c1838361102a565b6104cb91906120ca565b61114c565b8542101580156104e05750844211155b6105315760405162461bcd60e51b815260206004820152601e60248201527f4e6f742077697468696e20746865206d696e74696e672077696e646f772e000060448201526064015b60405180910390fd5b5f888152600c602052604090205460ff16156105995760405162461bcd60e51b815260206004820152602160248201527f546869732049442068617320616c7265616479206265656e20636c61696d65646044820152601760f91b6064820152608401610528565b5f811561067057604080513360208201529081018a9052606081018990526080810188905260a081018790524660c08201523060e08201525f90610100016040516020818303038152906040528051906020012090505f610626827f19457468657265756d205369676e6564204d6573736167653a0a3332000000005f908152601c91909152603c902090565b90506106678186868080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061148a92505050565b92505050610673565b50335b60408051602081018b90526001600160a01b03831691810191909152606081018990526080810188905260a081018790525f9060c0016040516020818303038152906040528051906020012090506107208686808060200260200160405190810160405280939291908181526020018383602002808284375f920191909152507f45711452762d9dab167c16cc1f8672d6b3102251471bbf07113c1e1134d39eea92508591506114b29050565b6107645760405162461bcd60e51b815260206004820152601560248201527424b73b30b634b21026b2b935b63290383937b7b31760591b6044820152606401610528565b5f8a8152600c60205260409020805460ff19166001179055610786338a6114c7565b604080518a815242602082015233918c917fd9cb1e2714d65a111c0f20f060176ad657496bd47a3de04ec7c3d4ca232112ac910160405180910390a350505050505050505050565b5f811161081d5760405162461bcd60e51b815260206004820152601b60248201527f4f6173653a204d7573742073617665206d6f7265207468616e203000000000006044820152606401610528565b6305f5e1008110156108715760405162461bcd60e51b815260206004820152601d60248201527f4f6173653a204d696e696d756d2073617665206973203120746f6b656e0000006044820152606401610528565b600160481b81106108d55760405162461bcd60e51b815260206004820152602860248201527f4f6173653a20416d6f756e7420746f6f206c6172676520666f72206576656e74604482015267207061636b696e6760c01b6064820152608401610528565b335f908152602081905260409020548111156109335760405162461bcd60e51b815260206004820152601a60248201527f4f6173653a20696e73756666696369656e742062616c616e63650000000000006044820152606401610528565b335f908152600b60205260409020546064116109ae5760405162461bcd60e51b815260206004820152603460248201527f4f6173653a205573657220686173207265616368656420746865206d6178696d604482015273756d206e756d626572206f6620736176696e677360601b6064820152608401610528565b6109b6611d7f565b6109be611d7f565b6109c88282611584565b5f6109da836109d56115dc565b61161a565b9050806109f95760405162461bcd60e51b8152600401610528906120dd565b600a80545f91908290610a129064ffffffffff1661213a565b91906101000a81548164ffffffffff021916908364ffffffffff160217905590505f845f0151866305f5e100610a48919061215d565b610a529190612174565b90505f8111610ab95760405162461bcd60e51b815260206004820152602d60248201527f4f6173653a20416d6f756e7420746f6f20736d616c6c20666f7220637572726560448201526c6e74207368617265207261746560981b6064820152608401610528565b600160481b8110610b1d5760405162461bcd60e51b815260206004820152602860248201527f4f6173653a2053686172657320746f6f206c6172676520666f72206576656e74604482015267207061636b696e6760c01b6064820152608401610528565b610b273387611721565b5f85608001516001610b3991906120ca565b90505f610b48610171836120ca565b9050600b5f336001600160a01b03166001600160a01b031681526020019081526020015f206040518060a001604052808664ffffffffff1681526020018581526020018a815260200184815260200183815250908060018154018082558091505060019003905f5260205f2090600502015f909190919091505f820151815f015f6101000a81548164ffffffffff021916908364ffffffffff1602179055506020820151816001015560408201518160020155606082015181600301556080820151816004015550508787604001818151610c2391906120ca565b905250606087018051849190610c3a9083906120ca565b90525060405164ffffffffff80861691339142166dffffffffffffffffff000000000060288d901b161768ffffffffffffffffff60701b607088901b1617907f390cc673cefaad1ca89a4ddf40d5a3199e7ffab1ad3d1c408fa22e7e473102a7905f90a4610ca88787611849565b5050505050505050565b610cba611d7f565b610cc2611d7f565b610ccc8282611584565b5f610cd56115dc565b90505f610ce2848361161a565b905080610d015760405162461bcd60e51b8152600401610528906120dd565b335f908152600b602052604081208054909103610d535760405162461bcd60e51b815260206004820152601060248201526f4f6173653a206e6f20736176696e677360801b6044820152606401610528565b80548710610d995760405162461bcd60e51b815260206004820152601360248201527209ec2e6ca7440d2dcecc2d8d2c840d2dcc8caf606b1b6044820152606401610528565b5f818881548110610dac57610dac612193565b5f9182526020909120600590910201805490915064ffffffffff888116911614610e185760405162461bcd60e51b815260206004820152601760248201527f4f6173653a20736176696e674964206d69736d617463680000000000000000006044820152606401610528565b806004015486608001511015610e705760405162461bcd60e51b815260206004820152601860248201527f4f6173653a20736176696e67206e6f74206d61747572656400000000000000006044820152606401610528565b5f6305f5e100875f01518360010154610e89919061215d565b610e939190612174565b8254600184015460405192935064ffffffffff918216923392607086901b68ffffffffffffffffff60701b1660289390931b6dffffffffffffffffff000000000016429091161791909117907ff0019f5bb966673bc3d326e7d459457783cb80b7edb41cf11164fbc23e9d2f93905f90a4816002015487604001818151610f1a91906121a7565b9052506001820154606088018051610f339083906121a7565b905250610f40838a6118ac565b610f4a8787611849565b610f5433826114c7565b505050505050505050565b6060600480546103f29061207e565b5f3381610f7b828661102a565b905083811015610fdb5760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152608401610528565b6104a4828686840361114c565b5f336104808185856112e8565b610ffd611d7f565b611005611d7f565b61100f8282611584565b61101b826109d56115dc565b506110268282611849565b5050565b6001600160a01b039182165f90815260016020908152604080832093909416825291909152205490565b6001600160a01b0381165f908152600b60209081526040808320805482518185028101850190935280835260609492939192909184015b828210156110ef575f8481526020908190206040805160a08101825260058602909201805464ffffffffff1683526001808201548486015260028201549284019290925260038101546060840152600401546080830152908352909201910161108b565b505050509050919050565b600b602052815f5260405f208181548110611113575f80fd5b5f9182526020909120600590910201805460018201546002830154600384015460049094015464ffffffffff9093169550909350919085565b6001600160a01b0383166111ae5760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610528565b6001600160a01b03821661120f5760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610528565b6001600160a01b038381165f8181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b5f61127b848461102a565b90505f1981146112e257818110156112d55760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610528565b6112e2848484840361114c565b50505050565b6001600160a01b03831661134c5760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610528565b6001600160a01b0382166113ae5760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610528565b6001600160a01b0383165f90815260208190526040902054818110156114255760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610528565b6001600160a01b038481165f81815260208181526040808320878703905593871680835291849020805487019055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a36112e2565b5f805f806114988686611996565b9250925092506114a882826119df565b5090949350505050565b5f826114be8584611a97565b14949350505050565b6001600160a01b03821661151d5760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610528565b8060025f82825461152e91906120ca565b90915550506001600160a01b0382165f81815260208181526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b60055482526006546020830152600754604083015260085460608301526115a96115dc565b60808381019182528351835260208085015190840152604080850151908401526060938401519383019390935251910152565b5f6201518061160b7f0000000000000000000000000000000000000000000000000000000068e4ce2f426121a7565b6116159190612174565b905090565b5f808360200151831161162d575f61163c565b602084015161163c90846121a7565b9050805f0361164f576001915050610486565b5f610171821161165f5781611663565b6101715b90505f81866020015161167691906120ca565b60208701519091505b818110156116d25760408051602081019091528751815260095f6116a48460016120ca565b815260208101919091526040015f20905190556116c087611ae3565b806116ca816121ba565b91505061167f565b5060208681018290528651604051908152829142917f8caa10406f5fa75d56a3b35f15b317829cd60a331e7048de76e042d088203bfb910160405180910390a350505060209092015114919050565b6001600160a01b0382166117815760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b6064820152608401610528565b6001600160a01b0382165f90815260208190526040902054818110156117f45760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b6064820152608401610528565b6001600160a01b0383165f818152602081815260408083208686039055600280548790039055518581529192917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9101611263565b8051825114158061186257508060200151826020015114155b8061187557508060400151826040015114155b8061188857508060600151826060015114155b15611026575080516005556020810151600655604081015160075560600151600855565b81545f906118bc906001906121a7565b9050808214611947578281815481106118d7576118d7612193565b905f5260205f2090600502018383815481106118f5576118f5612193565b5f91825260209091208254600590920201805464ffffffffff191664ffffffffff9092169190911781556001808301549082015560028083015490820155600380830154908201556004918201549101555b82805480611957576119576121d2565b5f8281526020812060055f1990930192830201805464ffffffffff19168155600181018290556002810182905560038101829055600401559055505050565b5f805f83516041036119cd576020840151604085015160608601515f1a6119bf88828585611bcd565b9550955095505050506119d8565b505081515f91506002905b9250925092565b5f8260038111156119f2576119f26121e6565b036119fb575050565b6001826003811115611a0f57611a0f6121e6565b03611a2d5760405163f645eedf60e01b815260040160405180910390fd5b6002826003811115611a4157611a416121e6565b03611a625760405163fce698f760e01b815260048101829052602401610528565b6003826003811115611a7657611a766121e6565b03611026576040516335e2f38360e21b815260048101829052602401610528565b5f81815b8451811015611adb57611ac782868381518110611aba57611aba612193565b6020026020010151611c95565b915080611ad3816121ba565b915050611a9b565b509392505050565b5f611aed82611cc4565b9050805f03611afa575050565b5f611b0c8266014f9a81ec1000612174565b90505f633b9aca008211611b5f5761016d611b28600284612174565b611b36906305f5e1006120ca565b611b44846305f5e10061215d565b611b4e9190612174565b611b589190612174565b9050611ba0565b61016d611b6d600384612174565b611b7b906305f5e1006120ca565b611b89846305f5e10061215d565b611b939190612174565b611b9d9190612174565b90505b6305f5e100611baf82826120ca565b8551611bbb919061215d565b611bc59190612174565b909352505050565b5f80807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841115611c0657505f91506003905082611c8b565b604080515f808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa158015611c57573d5f803e3d5ffd5b5050604051601f1901519150506001600160a01b038116611c8257505f925060019150829050611c8b565b92505f91508190505b9450945094915050565b5f818310611caf575f828152602084905260409020611cbd565b5f8381526020839052604090205b9392505050565b5f80611ccf60025490565b90505f83606001515f1480611ce357508351155b15611cef57505f611d12565b835160608501516305f5e10091611d059161215d565b611d0f9190612174565b90505b5f611d1d82846120ca565b9050805f03611d3057505f949350505050565b815f03611d4157505f949350505050565b5f81611d51846305f5e10061215d565b611d5b9190612174565b9050620186a0811015611d765750620186a095945050505050565b95945050505050565b6040518060a001604052805f81526020015f81526020015f81526020015f81526020015f81525090565b5f6020808352835180828501525f5b81811015611dd457858101830151858201604001528201611db8565b505f604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b0381168114611e0a575f80fd5b919050565b5f8060408385031215611e20575f80fd5b611e2983611df4565b946020939093013593505050565b5f805f60608486031215611e49575f80fd5b611e5284611df4565b9250611e6060208501611df4565b9150604084013590509250925092565b5f8083601f840112611e80575f80fd5b50813567ffffffffffffffff811115611e97575f80fd5b602083019150836020828501011115611eae575f80fd5b9250929050565b5f805f805f805f8060c0898b031215611ecc575f80fd5b88359750602089013596506040890135955060608901359450608089013567ffffffffffffffff80821115611eff575f80fd5b818b0191508b601f830112611f12575f80fd5b813581811115611f20575f80fd5b8c60208260051b8501011115611f34575f80fd5b6020830196508095505060a08b0135915080821115611f51575f80fd5b50611f5e8b828c01611e70565b999c989b5096995094979396929594505050565b5f60208284031215611f82575f80fd5b5035919050565b5f60208284031215611f99575f80fd5b611cbd82611df4565b5f8060408385031215611fb3575f80fd5b82359150602083013564ffffffffff81168114611fce575f80fd5b809150509250929050565b5f8060408385031215611fea575f80fd5b611ff383611df4565b915061200160208401611df4565b90509250929050565b602080825282518282018190525f919060409081850190868401855b82811015612071578151805164ffffffffff16855286810151878601528581015186860152606080820151908601526080908101519085015260a09093019290850190600101612026565b5091979650505050505050565b600181811c9082168061209257607f821691505b6020821081036120b057634e487b7160e01b5f52602260045260245ffd5b50919050565b634e487b7160e01b5f52601160045260245ffd5b80820180821115610486576104866120b6565b6020808252603a908201527f4f6173653a204461696c7920646174612075706461746520696e636f6d706c6560408201527f74652c2063616c6c207570646174654461696c79446174612829000000000000606082015260800190565b5f64ffffffffff808316818103612153576121536120b6565b6001019392505050565b8082028115828204841417610486576104866120b6565b5f8261218e57634e487b7160e01b5f52601260045260245ffd5b500490565b634e487b7160e01b5f52603260045260245ffd5b81810381811115610486576104866120b6565b5f600182016121cb576121cb6120b6565b5060010190565b634e487b7160e01b5f52603160045260245ffd5b634e487b7160e01b5f52602160045260245ffdfea2646970667358221220111f3bb3d96f253e4b108a2bb38a7cf98633167ff954618312c447ede28f1dbc64736f6c63430008140033