KiftDissolution

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 v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../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.
 *
 * The initial owner is set to the address provided by the deployer. 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;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

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

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @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 {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @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 {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _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/token/ERC721/IERC721.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC721/IERC721.sol)

pragma solidity >=0.6.2;

import {IERC165} from "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC-721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC-721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
     *   {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the address zero.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}
"
    },
    "@openzeppelin/contracts/utils/Context.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

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

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

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}
"
    },
    "@openzeppelin/contracts/utils/introspection/IERC165.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)

pragma solidity >=0.4.16;

/**
 * @dev Interface of the ERC-165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[ERC].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
"
    },
    "@openzeppelin/contracts/utils/ReentrancyGuard.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;

/**
 * @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 EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * 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;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    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
        if (_status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        // 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;
    }
}
"
    },
    "contracts/KiftDissolution.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";

/**
 * @title KiftDissolution
 * @dev Handles Kift DAO dissolution with a fair distribution mechanism
 * Phase 1: NFT holders register their NFTs for redemption (30 days) - NFTs stay in wallets
 * Phase 2: Calculate ETH per NFT based on actual registrations
 * Phase 3: Registered holders withdraw their ETH share
 */
contract KiftDissolution is Ownable, ReentrancyGuard {
    // ============ STATE VARIABLES ============

    IERC721 public immutable kiftablesNFT;

    uint256 public registrationStartTime;
    uint256 public constant REGISTRATION_PERIOD = 30 days;

    uint256 public totalETH;
    uint256 public totalNFTsRegistered;
    uint256 public ethPerNFT;
    bool public redemptionRateCalculated;
    bool public airdropInitiated;

    // Tracking registrations and withdrawals
    mapping(address => uint256) public userNFTCount;
    mapping(address => bool) public hasWithdrawn;
    mapping(uint256 => bool) public redeemedTokenIds;  // Tracks which NFTs have been redeemed
    mapping(address => uint256[]) public userTokenIds; // Optional: track which tokens each user registered
    mapping(address => bool) public hasRegistered;     // Track if user has registered any NFTs

    // Array of all users who registered NFTs for batch processing
    address[] public registeredUsers;
    uint256 public lastProcessedIndex;  // For batch airdrop processing

    // Optional: excluded addresses (e.g., Kift Corp)
    mapping(address => bool) public excludedAddresses;

    // ============ EVENTS ============

    event RegistrationPeriodStarted(uint256 startTime, uint256 endTime, uint256 totalETH);
    event NFTsRegistered(address indexed user, uint256[] tokenIds, uint256 count);
    event RedemptionRateCalculated(uint256 totalNFTs, uint256 ethPerNFT);
    event ETHWithdrawn(address indexed user, uint256 nftCount, uint256 ethAmount);
    event ETHAirdropped(address indexed user, uint256 nftCount, uint256 ethAmount);
    event AirdropInitiated(uint256 timestamp, uint256 totalUsers);
    event AirdropBatchProcessed(uint256 startIndex, uint256 endIndex, uint256 usersProcessed);
    event DonationWithdraw(address indexed to, uint256 amount);
    event EmergencyWithdraw(address indexed to, uint256 amount);

    // ============ MODIFIERS ============

    modifier registrationPeriodActive() {
        require(registrationStartTime > 0, "Registration period not started");
        require(block.timestamp >= registrationStartTime, "Registration period not yet active");
        require(block.timestamp < registrationStartTime + REGISTRATION_PERIOD, "Registration period ended");
        _;
    }

    modifier registrationPeriodEnded() {
        require(registrationStartTime > 0, "Registration period not started");
        require(block.timestamp >= registrationStartTime + REGISTRATION_PERIOD, "Registration period not ended");
        _;
    }

    modifier withdrawalActive() {
        require(redemptionRateCalculated, "Redemption rate not calculated");
        require(airdropInitiated, "Airdrop not initiated");
        _;
    }

    // ============ CONSTRUCTOR ============

    constructor(address _kiftablesNFT) Ownable(msg.sender) {
        require(_kiftablesNFT != address(0), "Invalid NFT address");
        kiftablesNFT = IERC721(_kiftablesNFT);
    }

    // ============ ADMIN FUNCTIONS ============

    /**
     * @dev Set excluded addresses (optional)
     */
    function setExcludedAddresses(address[] calldata _addresses, bool _excluded) external onlyOwner {
        require(registrationStartTime == 0, "Cannot modify after start");
        for (uint256 i = 0; i < _addresses.length; i++) {
            excludedAddresses[_addresses[i]] = _excluded;
        }
    }

    /**
     * @dev Start the registration period with treasury ETH
     */
    function startRegistrationPeriod() external payable onlyOwner {
        require(registrationStartTime == 0, "Already started");
        require(msg.value > 0, "No ETH provided");

        registrationStartTime = block.timestamp;
        totalETH += msg.value;

        emit RegistrationPeriodStarted(
            registrationStartTime,
            registrationStartTime + REGISTRATION_PERIOD,
            msg.value
        );
    }

    // ============ REGISTRATION FUNCTIONS ============

    /**
     * @dev Register NFTs for redemption during the registration period
     * NFTs remain in the user's wallet but are marked as redeemed
     * @param tokenIds Array of NFT token IDs to register
     */
    function registerNFTs(uint256[] calldata tokenIds) external registrationPeriodActive nonReentrant {
        require(tokenIds.length > 0, "No tokens provided");
        require(!excludedAddresses[msg.sender], "Address excluded");

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

            // Verify ownership - NFT stays with owner
            require(kiftablesNFT.ownerOf(tokenId) == msg.sender, "Not token owner");
            require(!redeemedTokenIds[tokenId], "Token already redeemed");

            // Mark as redeemed (but don't transfer)
            redeemedTokenIds[tokenId] = true;

            // Optional: track which tokens this user registered
            userTokenIds[msg.sender].push(tokenId);
        }

        // Track user if first time registering
        if (!hasRegistered[msg.sender]) {
            hasRegistered[msg.sender] = true;
            registeredUsers.push(msg.sender);
        }

        // Update user's registration count
        userNFTCount[msg.sender] += tokenIds.length;
        totalNFTsRegistered += tokenIds.length;

        emit NFTsRegistered(msg.sender, tokenIds, tokenIds.length);
    }

    // ============ CALCULATION FUNCTION ============

    /**
     * @dev Calculate the redemption rate after registration period ends
     * Simple calculation: totalETH / totalNFTsRegistered
     * Gas fees paid from Gnosis Safe wallet balance
     * Can be called by anyone
     */
    function calculateRedemptionRate() external registrationPeriodEnded {
        require(!redemptionRateCalculated, "Already calculated");
        require(totalNFTsRegistered > 0, "No NFTs registered");

        // Simple calculation - gas fees paid separately from Gnosis Safe
        ethPerNFT = totalETH / totalNFTsRegistered;
        redemptionRateCalculated = true;

        emit RedemptionRateCalculated(totalNFTsRegistered, ethPerNFT);
    }

    // ============ AIRDROP FUNCTIONS ============

    /**
     * @dev Initiate the airdrop process after rate calculation
     * Can be called days after the 30-day period for flexibility
     */
    function initiateAirdrop() external onlyOwner {
        require(redemptionRateCalculated, "Redemption rate not calculated");
        require(!airdropInitiated, "Airdrop already initiated");

        airdropInitiated = true;

        emit AirdropInitiated(block.timestamp, registeredUsers.length);
    }

    /**
     * @dev Batch airdrop ETH to registered users
     * @param batchSize Number of users to process in this batch
     */
    function airdropETH(uint256 batchSize) external onlyOwner withdrawalActive nonReentrant {
        require(batchSize > 0, "Batch size must be greater than 0");

        uint256 totalUsers = registeredUsers.length;
        require(lastProcessedIndex < totalUsers, "All users processed");

        uint256 startIndex = lastProcessedIndex;
        uint256 endIndex = startIndex + batchSize;
        if (endIndex > totalUsers) {
            endIndex = totalUsers;
        }

        uint256 successfulTransfers = 0;

        for (uint256 i = startIndex; i < endIndex; i++) {
            address user = registeredUsers[i];

            if (!hasWithdrawn[user] && userNFTCount[user] > 0) {
                uint256 nftCount = userNFTCount[user];
                uint256 ethAmount = nftCount * ethPerNFT;

                hasWithdrawn[user] = true;

                // Use low-level call for gas efficiency
                (bool success, ) = payable(user).call{value: ethAmount, gas: 30000}("");

                if (success) {
                    successfulTransfers++;
                    emit ETHAirdropped(user, nftCount, ethAmount);
                } else {
                    // If transfer fails, mark as not withdrawn so they can claim manually
                    hasWithdrawn[user] = false;
                }
            }
        }

        lastProcessedIndex = endIndex;
        emit AirdropBatchProcessed(startIndex, endIndex, successfulTransfers);
    }

    /**
     * @dev Manual withdrawal for users who didn't receive airdrop
     * Fallback for failed airdrops or users who want to claim manually
     */
    function withdrawETH() external withdrawalActive nonReentrant {
        require(userNFTCount[msg.sender] > 0, "No NFTs registered");
        require(!hasWithdrawn[msg.sender], "Already withdrawn");

        uint256 nftCount = userNFTCount[msg.sender];
        uint256 ethAmount = nftCount * ethPerNFT;

        hasWithdrawn[msg.sender] = true;

        emit ETHWithdrawn(msg.sender, nftCount, ethAmount);

        (bool success, ) = payable(msg.sender).call{value: ethAmount}("");
        require(success, "ETH transfer failed");
    }

    // ============ VIEW FUNCTIONS ============

    /**
     * @dev Get current state and statistics
     */
    function getRedemptionInfo() external view returns (
        uint256 startTime,
        uint256 endTime,
        uint256 totalEthAvailable,
        uint256 totalNftsRegistered,
        uint256 ethPerNftRate,
        bool rateCalculated,
        bool registrationsOpen,
        bool airdropStarted
    ) {
        startTime = registrationStartTime;
        endTime = registrationStartTime > 0 ? registrationStartTime + REGISTRATION_PERIOD : 0;
        totalEthAvailable = totalETH;
        totalNftsRegistered = totalNFTsRegistered;
        ethPerNftRate = ethPerNFT;
        rateCalculated = redemptionRateCalculated;
        registrationsOpen = registrationStartTime > 0 &&
                          block.timestamp >= registrationStartTime &&
                          block.timestamp < registrationStartTime + REGISTRATION_PERIOD;
        airdropStarted = airdropInitiated;
    }

    /**
     * @dev Get user-specific information
     */
    function getUserInfo(address user) external view returns (
        uint256 nftsRegistered,
        bool withdrawn,
        uint256 ethClaimable
    ) {
        nftsRegistered = userNFTCount[user];
        withdrawn = hasWithdrawn[user];
        ethClaimable = redemptionRateCalculated ? nftsRegistered * ethPerNFT : 0;
    }

    /**
     * @dev Get the token IDs registered by a specific user
     */
    function getUserTokenIds(address user) external view returns (uint256[] memory) {
        return userTokenIds[user];
    }

    /**
     * @dev Check if a specific token ID has been redeemed
     */
    function isTokenRedeemed(uint256 tokenId) external view returns (bool) {
        return redeemedTokenIds[tokenId];
    }

    /**
     * @dev Time until registrations close
     */
    function timeUntilRegistrationsClose() external view returns (uint256) {
        if (registrationStartTime == 0) return type(uint256).max;

        uint256 endTime = registrationStartTime + REGISTRATION_PERIOD;
        if (block.timestamp >= endTime) return 0;

        return endTime - block.timestamp;
    }

    /**
     * @dev Check if withdrawals are available
     */
    function canWithdraw() external view returns (bool) {
        return redemptionRateCalculated;
    }

    /**
     * @dev Get airdrop progress information
     */
    function getAirdropProgress() external view returns (
        uint256 totalUsers,
        uint256 processedUsers,
        uint256 remainingUsers
    ) {
        totalUsers = registeredUsers.length;
        processedUsers = lastProcessedIndex;
        remainingUsers = totalUsers > lastProcessedIndex ? totalUsers - lastProcessedIndex : 0;
    }

    /**
     * @dev Get the total number of registered users
     */
    function getRegisteredUsersCount() external view returns (uint256) {
        return registeredUsers.length;
    }

    // ============ DONATION & EMERGENCY FUNCTIONS ============

    /**
     * @dev Withdraw remaining funds for donation after 60 days
     * @param donationAddress Address to send the funds to
     */
    function donationWithdraw(address donationAddress) external onlyOwner {
        require(registrationStartTime > 0, "Not started");
        require(block.timestamp > registrationStartTime + 60 days, "Too early for donation withdrawal");
        require(donationAddress != address(0), "Invalid donation address");

        uint256 balance = address(this).balance;
        require(balance > 0, "No funds to withdraw");

        emit DonationWithdraw(donationAddress, balance);

        (bool success, ) = payable(donationAddress).call{value: balance}("");
        require(success, "Transfer failed");
    }

    /**
     * @dev Emergency withdraw after 6 months for any unclaimed funds
     */
    function emergencyWithdraw() external onlyOwner {
        require(registrationStartTime > 0, "Not started");
        require(block.timestamp > registrationStartTime + 180 days, "Too early for emergency withdrawal");

        uint256 balance = address(this).balance;
        emit EmergencyWithdraw(msg.sender, balance);

        (bool success, ) = payable(msg.sender).call{value: balance}("");
        require(success, "Transfer failed");
    }

    // ============ RECEIVE FUNCTION ============

    receive() external payable {
        totalETH += msg.value;
    }
}"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": true,
      "runs": 200
    },
    "viaIR": false,
    "evmVersion": "paris",
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    }
  }
}}

Tags:
ERC721, ERC165, Multisig, Non-Fungible, Upgradeable, Multi-Signature, Factory|addr:0x34db9dffe532bc54cf50c40228696b915fcb1df1|verified:true|block:23695504|tx:0x1c22fb4dbba7366291ff342685a0681a14a6c91a6ef6618f854f063a4ffa5241|first_check:1761911075

Submitted on: 2025-10-31 12:44:36

Comments

Log in to comment.

No comments yet.