CHADSxPOPO

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "@openzeppelin/contracts/access/Ownable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
"
    },
    "@openzeppelin/contracts/security/ReentrancyGuard.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)

pragma solidity ^0.8.0;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == _ENTERED;
    }
}
"
    },
    "@openzeppelin/contracts/utils/Address.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     *
     * Furthermore, `isContract` will also return true if the target contract within
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
     * which only has an effect at the end of a transaction.
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    function _revert(bytes memory returndata, string memory errorMessage) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }
}
"
    },
    "@openzeppelin/contracts/utils/Context.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @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;
    }
}
"
    },
    "@openzeppelin/contracts/utils/math/Math.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

    /**
     * @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 up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (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; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                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.
            require(denominator > prod1, "Math: mulDiv overflow");

            ///////////////////////////////////////////////
            // 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.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            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 (rounding == Rounding.Up && 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 down.
     *
     * 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 + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * 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 + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * 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 + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * 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 + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
        }
    }
}
"
    },
    "@openzeppelin/contracts/utils/math/SignedMath.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.0;

/**
 * @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);
        }
    }
}
"
    },
    "@openzeppelin/contracts/utils/Strings.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)

pragma solidity ^0.8.0;

import "./math/Math.sol";
import "./math/SignedMath.sol";

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

    /**
     * @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), _SYMBOLS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toString(int256 value) internal pure returns (string memory) {
        return string(abi.encodePacked(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) {
        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] = _SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        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 keccak256(bytes(a)) == keccak256(bytes(b));
    }
}
"
    },
    "contracts/CHADSxPOPO.sol": {
      "content": "//           @@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@@@@@@@@@@@@            @@@@@@@@
//           @@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@@@@@@@@@@@@            @@@@@@@@
//           @@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@@@@@@@@@@@@            @@@@@@@@
//     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@@@@@@@@@@@@            @@@@@@@@
//     @@@@@@@@@@@@@@                         @@@@@@@@@@@@@@            @@@@@@@@
//     @@@@@@@@@@@@@@                         @@@@@@@@@@@@@@            @@@@@@@@
//     @@@@@@@@@@@@@@                         @@@@@@@@@@@@@@            @@@@@@@@
//     @@@@@@@@@@@@@@                         @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//     @@@@@@@@@@@@@@                         @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//     @@@@@@@@@@@@@@                         @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//     @@@@@@@@@@@@@@                         @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//     @@@@@@@@@@@@@@                         @@@@@@@@@@@@@@            @@@@@@@@
//     @@@@@@@@@@@@@@                         @@@@@@@@@@@@@@            @@@@@@@@
//     @@@@@@@@@@@@@@                         @@@@@@@@@@@@@@            @@@@@@@@
//     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@@@@@@@@@@@@            @@@@@@@@
//           @@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@@@@@@@@@@@@            @@@@@@@@
//           @@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@@@@@@@@@@@@            @@@@@@@@
//           @@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@@@@@@@@@@@@            @@@@@@@@

//                  @@@@@@@@@@@@@@            @@@@@@@@@@@@@@@@@@@@@@@@@@@
//                 @@@@@@@@@@@@@@@            @@@@@@@@@@@@@@@@@@@@@@@@@@@@
//                 @@@@@@@@@@@@@@@            @@@@@@@@@@@@@@@@@@@@@@@@@@@@
//                 @@@@@@@@@@@@@@@            @@@@@@@@@@@@@@@@@@@@@@@@@@@@
//           @@@@@@@@            @@@@@@@      @@@@@@@@@@@@@@            @@@@@@@@
//           @@@@@@@@            @@@@@@@      @@@@@@@@@@@@@@            @@@@@@@@
//           @@@@@@@@            @@@@@@@      @@@@@@@@@@@@@@            @@@@@@@@
//     @@@@@@@@@@@@@@            @@@@@@@      @@@@@@@@@@@@@@            @@@@@@@@
//     @@@@@@@@@@@@@@            @@@@@@@      @@@@@@@@@@@@@@            @@@@@@@@
//     @@@@@@@@@@@@@@            @@@@@@@      @@@@@@@@@@@@@@            @@@@@@@@
//     @@@@@@@@@@@@@@            @@@@@@@      @@@@@@@@@@@@@@            @@@@@@@@
//     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@@@@@@@@@@@@            @@@@@@@@
//     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@@@@@@@@@@@@            @@@@@@@@
//     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@@@@@@@@@@@@            @@@@@@@@
//     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@@@@@@@@@@@@            @@@@@@@@
//     @@@@@@@@@@@@@@            @@@@@@@      @@@@@@@@@@@@@@@@@@@@@@@@@@@@
//     @@@@@@@@@@@@@@            @@@@@@@      @@@@@@@@@@@@@@@@@@@@@@@@@@@@
//     @@@@@@@@@@@@@@            @@@@@@@      @@@@@@@@@@@@@@@@@@@@@@@@@@@@
//     @@@@@@@@@@@@@@            @@@@@@@      @@@@@@@@@@@@@@@@@@@@@@@@@@@
//                                                                           @@@@@@@@@
//                                                                           @@@@@@@@@
//                                                                               @@@@@
//     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@
//     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@                              @@@ @@@
//     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@                              @@@  @@
//     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@                              @@@
//     @@@@@@@@@@@@@@                         @@@                              @@@
//     @@@@@@@@@@@@@@                         @@@                              @@@
//     @@@@@@@@@@@@@@                         @@@  @@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@
//     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@  @@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@
//     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@      @@@@@@@@@@   @@@@@@@@@@ @@@
//     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@    @@  @@@@@        @@@@@@   @@@
//     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@    @@@                       @@@
//                               @@@@@@@      @@@      @@@@@@@@@@@@@@@@@@@@@@@ @@@
//                               @@@@@@@      @@@                              @@@
//                               @@@@@@@      @@@                              @@@
//     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@                              @@@
//     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@                              @@@
//     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@                              @@@
//     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

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

import "erc721a/contracts/ERC721A.sol";
import "erc721a/contracts/extensions/ERC721AQueryable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/Address.sol";

/**
 * @title CHADS x POPO
 * @dev A simple NFT contract using ERC721A for efficient batch minting
 */
contract CHADSxPOPO is ERC721A, ERC721AQueryable, Ownable, ReentrancyGuard {
    using Strings for uint256;
    using Address for address payable;

    // Maximum supply of NFTs
    uint256 public constant MAX_SUPPLY = 5000;

    // Maximum number of NFTs that can be minted per transaction
    uint256 public constant MAX_PER_TX = 20;

    // Maximum tier level
    uint256 public constant MAX_TIER = 7;

    // Price per NFT
    uint256 public price = 0.005000 ether;

    // Base URI for metadata
    string private _baseTokenURI;

    // Whether public minting is enabled
    bool public publicMintEnabled = false;

    // Mapping to track mint timestamp for each token (used for seed generation)
    mapping(uint256 => uint256) public tokenMintTimestamp;

    // Mapping to track which tier each token belongs to
    mapping(uint256 => uint256) public tokenTier;

    // Mapping to track payment amount for each token (for refund calculation)
    mapping(uint256 => uint256) public tokenPayment;

    // Mapping to track if a token has been refunded
    mapping(uint256 => bool) public tokenRefunded;

    // Mapping to track if a token has ever been Snagged (ineligible for refund)
    mapping(uint256 => bool) public tokenEverSnagged;

    // Mapping to track if a token is locked
    mapping(uint256 => bool) public tokenLocked;

    // Mapping to track the locked seed for each token
    mapping(uint256 => uint256) public tokenLockedSeed;

    // Collection lock state
    bool public collectionLocked = false;
    uint256 public collectionLockedTimePeriod = 0;

    // Address that can call lockCollection function
    address public lockCollectionAddress;

    // Image change trigger state
    uint256 public lastTriggerTime = 0;
    uint256 public currentRandomSeed = 0;

    // Events
    event PublicMintToggled(bool enabled);
    event PriceUpdated(uint256 newPrice);
    event BaseURIUpdated(string newBaseURI);
    event TokenRefunded(
        uint256 indexed tokenId,
        address indexed owner,
        uint256 amount
    );
    event TokenSnagged(
        uint256 indexed tokenId,
        address indexed from,
        address indexed to,
        uint256 newTier,
        uint256 snagPrice,
        uint256 compensation
    );
    event TokenUpgraded(
        uint256 indexed tokenId,
        address indexed owner,
        uint256 oldTier,
        uint256 newTier,
        uint256 upgradeCost
    );
    event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId);
    event MetadataUpdate(uint256 _tokenId);
    event EtherReceived(address indexed sender, uint256 amount);
    event TokenLocked(
        uint256 indexed tokenId,
        address indexed owner,
        uint256 lockedSeed
    );
    event TokenUnlocked(uint256 indexed tokenId, address indexed owner);
    event CollectionLocked(uint256 lockedTimePeriod);
    event ImageChangeTriggered(
        address indexed triggerer,
        uint256 randomSeed,
        uint256 triggerTime
    );

    constructor(
        string memory name,
        string memory symbol,
        string memory baseURI
    ) ERC721A(name, symbol) {
        _baseTokenURI = baseURI;
    }

    /**
     * @dev Public mint function
     * @param quantity Number of NFTs to mint
     * @param tier Tier level (1-based, where 1 is cheapest)
     */
    function mint(
        uint256 quantity,
        uint256 tier
    ) external payable nonReentrant {
        require(publicMintEnabled, "Public minting is not enabled");
        require(
            !collectionLocked,
            "Minting disabled once collection is locked"
        );
        require(quantity > 0, "Quantity must be greater than 0");
        require(quantity <= MAX_PER_TX, "Exceeds maximum per transaction");
        require(tier > 0, "Tier must be greater than 0");
        require(tier <= MAX_TIER, "Tier exceeds maximum allowed");
        require(
            totalSupply() + quantity <= MAX_SUPPLY,
            "Exceeds maximum supply"
        );

        uint256 tierPrice = getTierPrice(tier);
        require(msg.value >= tierPrice * quantity, "Insufficient payment");

        uint256 startTokenId = _nextTokenId();
        uint256 mintTimestamp = block.timestamp;
        _mint(msg.sender, quantity);

        // Assign tier, payment amount, and mint timestamp to each minted token
        for (uint256 i = 0; i < quantity; i++) {
            tokenTier[startTokenId + i] = tier;
            tokenPayment[startTokenId + i] = tierPrice;
            tokenMintTimestamp[startTokenId + i] = mintTimestamp;
        }
        if (totalSupply() == MAX_SUPPLY) {
            triggerImageChange();
        }

        // Refund excess payment if any using safe transfer
        uint256 totalCost = tierPrice * quantity;
        uint256 excessPayment = msg.value > totalCost
            ? msg.value - totalCost
            : 0;
        if (excessPayment > 0) {
            bool refundOk = _safeTransfer(msg.sender, excessPayment);
            require(refundOk, "Excess refund failed");
        }
    }

    /**
     * @dev Toggle public minting on/off
     */
    function togglePublicMint() external onlyOwner {
        publicMintEnabled = !publicMintEnabled;
        emit PublicMintToggled(publicMintEnabled);
    }

    /**
     * @dev Update the mint price
     * @param newPrice New price in wei
     */
    function setPrice(uint256 newPrice) external onlyOwner {
        // Validate that the new price won't cause overflow at maximum tier
        if (newPrice > 0) {
            uint256 maxMultiplier = 10 ** (MAX_TIER - 1);
            require(
                maxMultiplier <= type(uint256).max / newPrice,
                "Price too high: would cause overflow at max tier"
            );
        }

        price = newPrice;
        emit PriceUpdated(newPrice);
    }

    /**
     * @dev Set the address that can call lockCollection function
     * @param newAddress New address that can lock the collection
     */
    function setLockCollectionAddress(address newAddress) external onlyOwner {
        lockCollectionAddress = newAddress;
    }

    /**
     * @dev Calculate the price for a specific tier
     * @param tier Tier level (1-based)
     * @return tierPrice Price in wei for the tier
     */
    function getTierPrice(
        uint256 tier
    ) public view returns (uint256 tierPrice) {
        require(tier > 0, "Tier must be greater than 0");
        require(tier <= MAX_TIER, "Tier exceeds maximum allowed");

        // Price = basePrice * 10^(tier - 1)
        // Tier 1: price * 1 = price
        // Tier 2: price * 10
        // Tier 3: price * 100
        // Tier 4: price * 1000
        // Tier 5: price * 10000
        // Tier 6: price * 100000
        // Tier 7: price * 1000000

        // Use unchecked for gas optimization since we verify bounds
        uint256 multiplier;
        unchecked {
            multiplier = 10 ** (tier - 1);
        }

        // Check for multiplication overflow before performing the operation
        // If price * multiplier would overflow, revert
        require(
            price == 0 || multiplier <= type(uint256).max / price,
            "Price calculation would overflow"
        );

        // Safe to multiply after overflow check
        unchecked {
            return price * multiplier;
        }
    }

    /**
     * @dev Set the base URI for token metadata
     * @param baseURI New base URI
     */
    function setBaseURI(string calldata baseURI) external onlyOwner {
        _baseTokenURI = baseURI;
        emit BaseURIUpdated(baseURI);
    }

    /**
     * @dev Claim refund for tokens (burns tokens)
     * @param tokenIds Array of token IDs to refund
     */
    function claimRefund(uint256[] calldata tokenIds) external nonReentrant {
        require(totalSupply() < MAX_SUPPLY, "Refunds disabled after sellout");
        require(
            !collectionLocked,
            "Refunds disabled once collection is locked"
        );

        uint256 totalRefund = 0;
        address refundRecipient = msg.sender; // Cache to prevent changes during execution

        for (uint256 i = 0; i < tokenIds.length; i++) {
            uint256 tokenId = tokenIds[i];

            require(_exists(tokenId), "Token does not exist");
            require(ownerOf(tokenId) == refundRecipient, "Not token owner");
            require(!tokenRefunded[tokenId], "Token already refunded");
            require(
                !tokenEverSnagged[tokenId],
                "Token not eligible for refund"
            );

            // EFFECTS: Update state first
            tokenRefunded[tokenId] = true;
            totalRefund += tokenPayment[tokenId];

            // Burn the token
            _burn(tokenId);

            emit TokenRefunded(tokenId, refundRecipient, tokenPayment[tokenId]);
        }

        require(totalRefund > 0, "No refund amount");

        // Use safe transfer to prevent gas griefing
        bool success = _safeTransfer(refundRecipient, totalRefund);
        require(success, "Refund transfer failed");
    }

    /**
     * @dev Snag a token by paying a higher tier price
     * @param tokenId Token ID to snag
     * @param targetTier Target tier level (0 means auto-increment by 1)
     */
    function snagToken(
        uint256 tokenId,
        uint256 targetTier
    ) external payable nonReentrant {
        require(_exists(tokenId), "Token does not exist");
        require(!tokenRefunded[tokenId], "Token has been refunded");
        require(
            totalSupply() == MAX_SUPPLY,
            "Snagging only available after sellout"
        );
        require(
            !collectionLocked,
            "Snagging disabled once collection is locked"
        );

        address currentOwner = ownerOf(tokenId);
        require(currentOwner != msg.sender, "Cannot snag your own token");
        require(currentOwner != address(0), "Token has no owner");

        uint256 currentTier = tokenTier[tokenId];
        uint256 newTier;

        if (targetTier == 0) {
            // Auto-increment by 1
            newTier = currentTier + 1;
        } else {
            // Use specified target tier
            require(
                targetTier > currentTier,
                "Target tier must be higher than current tier"
            );
            newTier = targetTier;
        }

        require(newTier <= MAX_TIER, "Target tier exceeds maximum allowed");

        uint256 snagPrice = getTierPrice(newTier);
        require(msg.value >= snagPrice, "Insufficient payment for snag");

        // Calculate compensation for current owner (currentTier x current tier price)
        uint256 currentTierPrice = getTierPrice(currentTier);
        uint256 compensation = currentTierPrice * currentTier;

        // Cache addresses to prevent state changes during execution
        address snagger = msg.sender;
        uint256 excessPayment = msg.value > snagPrice
            ? msg.value - snagPrice
            : 0;

        tokenTier[tokenId] = newTier;
        tokenPayment[tokenId] = snagPrice;
        tokenEverSnagged[tokenId] = true;

        // Securely transfer token using internal function (bypasses approvals safely)
        _forceTransfer(currentOwner, snagger, tokenId);

        emit TokenSnagged(
            tokenId,
            currentOwner,
            snagger,
            newTier,
            snagPrice,
            compensation
        );

        // Emit metadata update event for the snagged token
        emit MetadataUpdate(tokenId);

        // Pay compensation to original owner using safe transfer
        bool compensationOk = _safeTransfer(currentOwner, compensation);
        require(compensationOk, "Compensation transfer failed");

        // Refund excess payment to snagger if any using safe transfer
        if (excessPayment > 0) {
            bool refundOk = _safeTransfer(snagger, excessPayment);
            require(refundOk, "Excess refund failed");
        }
    }

    /**
     * @dev Upgrade a token to a higher tier by paying the difference
     * @param tokenId Token ID to upgrade
     * @param newTier New tier level (must be higher than current)
     */
    function upgradeToken(
        uint256 tokenId,
        uint256 newTier
    ) external payable nonReentrant {
        require(_exists(tokenId), "Token does not exist");
        require(!tokenRefunded[tokenId], "Token has been refunded");
        require(ownerOf(tokenId) == msg.sender, "Not token owner");
        require(newTier > 0, "Tier must be greater than 0");

        uint256 currentTier = tokenTier[tokenId];
        require(
            newTier > currentTier,
            "New tier must be higher than current tier"
        );
        require(newTier <= MAX_TIER, "New tier exceeds maximum allowed");

        // Calculate upgrade cost (difference between new and current tier prices)
        uint256 currentTierPrice = getTierPrice(currentTier);
        uint256 newTierPrice = getTierPrice(newTier);
        uint256 upgradeCost = newTierPrice - currentTierPrice;

        require(msg.value >= upgradeCost, "Insufficient payment for upgrade");

        // EFFECTS: Update all state BEFORE external calls
        tokenTier[tokenId] = newTier;
        tokenPayment[tokenId] = newTierPrice;

        emit TokenUpgraded(
            tokenId,
            msg.sender,
            currentTier,
            newTier,
            upgradeCost
        );

        // Emit metadata update event for the upgraded token
        emit MetadataUpdate(tokenId);

        // Refund excess payment if any using safe transfer
        uint256 excessPayment = msg.value > upgradeCost
            ? msg.value - upgradeCost
            : 0;
        if (excessPayment > 0) {
            bool refundOk = _safeTransfer(msg.sender, excessPayment);
            require(refundOk, "Refund failed");
        }
    }

    /**
     * @dev Withdraw a specific amount of contract balance to the owner
     * @param amount Amount in wei to withdraw
     */
    function withdraw(uint256 amount) external onlyOwner {
        require(amount > 0, "Amount must be greater than 0");
        uint256 balance = address(this).balance;
        require(balance >= amount, "Insufficient contract balance");

        address recipient = owner(); // Cache owner to prevent changes during execution

        // INTERACTIONS: External call LAST using safe transfer
        bool success = _safeTransfer(recipient, amount);
        require(success, "Withdrawal failed");
    }

    /**
     * @dev Check if a token can be locked
     * @param tokenId Token ID to check
     * @return canLock Whether the token can be locked
     * @return reason Reason why it cannot be locked (empty if can be locked)
     */
    function canLockToken(
        uint256 tokenId
    ) external view returns (bool canLock, string memory reason) {
        if (!_exists(tokenId)) {
            return (false, "Token does not exist");
        }
        if (totalSupply() < MAX_SUPPLY) {
            return (false, "Lock after sellout");
        }
        if (collectionLocked) {
            return (false, "Collection is locked");
        }
        if (tokenLocked[tokenId]) {
            return (false, "Token already locked");
        }
        if (tokenRefunded[tokenId]) {
            return (false, "Cannot lock refunded token");
        }
        return (true, "");
    }

    /**
     * @dev Lock a token to lock its current seed
     * @param tokenId Token ID to lock
     */
    function lockToken(uint256 tokenId) external {
        require(totalSupply() >= MAX_SUPPLY, "Lock after sellout");
        require(_exists(tokenId), "Token does not exist");
        require(ownerOf(tokenId) == msg.sender, "Not token owner");
        require(!collectionLocked, "Collection locked: token lock disabled");
        require(!tokenLocked[tokenId], "Token already locked");
        require(!tokenRefunded[tokenId], "Cannot lock refunded token");

        // Store the current seed as the locked seed
        uint256 currentSeed = getTokenSeed(tokenId);
        tokenLocked[tokenId] = true;
        tokenLockedSeed[tokenId] = currentSeed;

        emit TokenLocked(tokenId, msg.sender, currentSeed);
    }

    /**
     * @dev Unlock a token to allow seed changes again
     * @param tokenId Token ID to unlock
     */
    function unlockToken(uint256 tokenId) external {
        require(totalSupply() >= MAX_SUPPLY, "Lock after sellout");
        require(_exists(tokenId), "Token does not exist");
        require(ownerOf(tokenId) == msg.sender, "Not token owner");
        require(!collectionLocked, "Collection locked: token unlock disabled");
        require(tokenLocked[tokenId], "Token not locked");

        tokenLocked[tokenId] = false;
        tokenLockedSeed[tokenId] = 0; // Clear the stored seed

        emit TokenUnlocked(tokenId, msg.sender);
    }

    /**
     * @dev Lock the entire collection (owner or authorized address only)
     * Locks all tokens to their current time period seed
     */
    function lockCollection() external {
        require(
            msg.sender == owner() || msg.sender == lockCollectionAddress,
            "Only owner or authorized address can lock collection"
        );
        require(!collectionLocked, "Collection already locked");

        collectionLocked = true;
        collectionLockedTimePeriod = lastTriggerTime;

        emit CollectionLocked(lastTriggerTime);
    }

    /**
     * @dev Trigger image change - can only be called once per 12-hour period
     * Only works after sellout when images are revealed
     */
    function triggerImageChange() public {
        require(
            !collectionLocked,
            "Cannot trigger image change when collection is locked"
        );
        require(
            totalSupply() >= MAX_SUPPLY,
            "Image changes only available after sellout"
        );

        // Check if already triggered for this time period
        uint256 currentPeriod = getCurrentTimePeriod();
        require(
            lastTriggerTime < currentPeriod,
            "Already triggered for this period"
        );

        // Generate new random seed using current block data
        uint256 randomSeed = uint256(
            keccak256(
                abi.encodePacked(
                    block.timestamp,
                    block.prevrandao,
                    msg.sender,
                    currentPeriod
                )
            )
        );

        // Update state
        lastTriggerTime = currentPeriod;
        currentRandomSeed = randomSeed;

        emit BatchMetadataUpdate(1, _totalMinted());
        emit ImageChangeTriggered(msg.sender, randomSeed, block.timestamp);
    }

    /**
     * @dev Receive function to accept plain ETH transfers
     */
    receive() external payable {
        emit EtherReceived(msg.sender, msg.value);
    }

    /**
     * @dev Fallback handler: revert with a clear message for unknown function calls
     */
    fallback() external payable {
        revert("Function does not exist");
    }

    /**
     * @dev Safe transfer function with gas limit to prevent gas griefing attacks
     * @param recipient Address to send ether to
     * @param amount Amount of ether to send
     * @return success Whether the transfer was successful
     */
    function _safeTransfer(
        address recipient,
        uint256 amount
    ) private returns (bool success) {
        if (amount == 0) return true;

        // Use Address.sendValue for safe transfers with built-in gas limits
        // This prevents gas griefing and reentrancy attacks
        // sendValue reverts on failure, so we use try-catch to return false instead
        try this._sendValueWrapper(payable(recipient), amount) {
            return true;
        } catch {
            return false;
        }
    }

    /**
     * @dev Wrapper function for Address.sendValue to enable try-catch usage
     * @param recipient Address to send ether to
     * @param amount Amount of ether to send
     */
    function _sendValueWrapper(
        address payable recipient,
        uint256 amount
    ) external {
        require(msg.sender == address(this), "Only self-call allowed");
        Address.sendValue(recipient, amount);
    }

    /**
     * @dev Secure internal transfer function for snagging mechanism
     * Uses a self-delegating call to bypass approval requirements safely
     */
    function _forceTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal {
        // Verify the transfer is valid
        require(_exists(tokenId), "Token does not exist");
        require(ownerOf(tokenId) == from, "From address is not owner");
        require(to != address(0), "Transfer to zero address");

        // Use a delegated call to the contract itself to perform the transfer
        // This way msg.sender will be the contract during the transfer
        bytes memory data = abi.encodeWithSignature(
            "executeTransfer(address,address,uint256)",
            from,
            to,
            tokenId
        );

        (bool success, ) = address(this).call(data);
        require(success, "Force transfer failed");
    }

    /**
     * @dev Internal function to execute transfer with contract as msg.sender
     * Only callable by the contract itself during snagging operations
     */
    function executeTransfer(
        address from,
        address to,
        uint256 tokenId
    ) external {
        require(msg.sender == address(this), "Only self-call allowed");

        // Temporarily approve this contract to transfer the token
        _approve(address(this), tokenId);

        // Now transfer the token - msg.sender is the contract
        transferFrom(from, to, tokenId);
    }

    /**
     * @dev Returns the starting token ID (ERC721A starts at 0 by default, this changes it to 1)
     */
    function _startTokenId() internal pure override returns (uint256) {
        return 1;
    }

    /**
     * @dev Returns the base URI for tokens
     */
    function _baseURI() internal view override returns (string memory) {
        return _baseTokenURI;
    }

    /**
     * @dev Returns the token URI for a given token ID
     * @param tokenId Token ID to get URI for
     */
    function tokenURI(
        uint256 tokenId
    ) public view override(ERC721A, IERC721A) returns (string memory) {
        if (!_exists(tokenId)) revert URIQueryForNonexistentToken();

        string memory baseURI = _baseURI();

        if (totalSupply() < MAX_SUPPLY) {
            // Before sellout, return placeholder metadata
            return
                bytes(baseURI).length != 0
                    ? string(abi.encodePacked(baseURI, tokenId.toString()))
                    : "";
        } else {
            // After sellout, include the seed in the URL
            uint256 seed = getTokenSeed(tokenId);
            return
                bytes(baseURI).length != 0
                    ? string(
                        abi.encodePacked(
                            baseURI,
                            tokenId.toString(),
                            "/",
                            seed.toString(),
                            "/",
                            tokenTier[tokenId].toString()
                        )
                    )
                    : "";
        }
    }

    /**
     * @dev Returns total number of tokens minted
     */
    function totalMinted() external view returns (uint256) {
        return _totalMinted();
    }

    /**
     * @dev Returns the number of tokens minted by a specific address
     * @param owner Address to check
     */
    function numberMinted(address owner) external view returns (uint256) {
        return _numberMinted(owner);
    }

    /**
     * @dev Returns whether a token exists
     * @param tokenId Token ID to check
     */
    function exists(uint256 tokenId) external view returns (bool) {
        return _exists(tokenId);
    }

    /**
     * @dev Get mint info for an address
     * @param minter Address to check
     */
    function getMintInfo(
        address minter
    )
        external
        view
        returns (
            uint256 numMinted,
            uint256 remainingSupply,
            uint256 currentPrice,
            bool canMint
        )
    {
        numMinted = _numberMinted(minter);
        remainingSupply = MAX_SUPPLY - totalSupply();
        currentPrice = price;
        canMint = publicMintEnabled && remainingSupply > 0 && !collectionLocked;
    }

    /**
     * @dev Get snag info for a token
     * @param tokenId Token ID to check
     * @param newTier Proposed new tier
     */
    function getSnagInfo(
        uint256 tokenId,
        uint256 newTier
    )
        external
        view
        returns (
            bool canSn

Tags:
ERC721, ERC165, Multisig, Non-Fungible, Voting, Upgradeable, Multi-Signature, Factory|addr:0xd90ff249e22b6cc994aedc690426c55e79445133|verified:true|block:23405609|tx:0x6a06bde0cee58ff748f26bd8f791c7dd05c1cd51d3d0c8b79bd62f74043e42b1|first_check:1758391044

Submitted on: 2025-09-20 19:57:26

Comments

Log in to comment.

No comments yet.