APE Price: $0.67 (+0.92%)

Contract Diff Checker

Contract Name:
ApeBombNftWinnerClaim

Contract Source Code:

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

interface INFTShadow {
    function locked(uint256 tokenId) external view returns (bool);
    function ownerOf(uint256 tokenId) external view returns (address);
}

interface IApeBomb {
    function owner() external view returns (address);
}

struct Winner {
    address contractAddress;
    uint96 amount;
    uint64 tokenId;
    uint40 timestamp;
    bool claimed;
    bool reclaimed;
}

contract ApeBombNftWinnerClaim {
    error NoWinner();
    error NotExpired();
    error NotApeBombContract();
    error InvalidCaller();
    error AlreadyClaimed();
    error TransferFailed();
    error TokenIsLocked();
    error NotOwner();

    event WinnerSet(uint256 tournamentId, Winner winner);
    event Claimed(uint256 tournamentId, address winner, uint256 amount);
    event Reclaimed(uint256 tournamentId, address winner, uint256 amount);

    mapping(uint256 tournamentId => Winner) public winners;
    address public immutable APE_BOMB_CONTRACT_ADDRESS;
    uint256 public constant EXPIRATION = 30 days;

    constructor(address _apeBombContract) {
        APE_BOMB_CONTRACT_ADDRESS = _apeBombContract;
    }

    /**
     * @dev Set the winner of the tournament.
     * @param tournamentId The ID of the tournament.
     * @param contractAddress The address of the NFT contract.
     * @param tokenId The ID of the NFT.
     */
    function setWinner(uint256 tournamentId, address contractAddress, uint256 tokenId) external payable {
        if (msg.sender != APE_BOMB_CONTRACT_ADDRESS) revert NotApeBombContract();

        winners[tournamentId] = Winner({
            contractAddress: contractAddress,
            amount: uint96(msg.value),
            tokenId: uint64(tokenId),
            timestamp: uint40(block.timestamp),
            claimed: false,
            reclaimed: false
        });

        emit WinnerSet(tournamentId, winners[tournamentId]);
    }

    /**
     * @dev Claim the winner's NFT.
     * @param tournamentId The ID of the tournament.
     */
    function claim(uint256 tournamentId) external {
        if (winners[tournamentId].amount == 0) revert NoWinner();

        INFTShadow nftShadow = INFTShadow(winners[tournamentId].contractAddress);
        if (winners[tournamentId].claimed || winners[tournamentId].reclaimed) revert AlreadyClaimed();

        winners[tournamentId].claimed = true;

        uint256 tokenId = winners[tournamentId].tokenId;
        if (nftShadow.locked(tokenId)) revert TokenIsLocked();
        if (nftShadow.ownerOf(tokenId) != msg.sender) revert NotOwner();

        (bool success,) = payable(msg.sender).call{value: winners[tournamentId].amount}("");
        if (!success) revert TransferFailed();

        emit Claimed(tournamentId, msg.sender, winners[tournamentId].amount);
    }

    /**
     * @dev If the winner does not claim within 30 days, 90% of the funds will be added to the current ApeBomb tournament
     * and the remaining 10% will be sent to the ApeBomb owner.
     * @param tournamentId The ID of the tournament.
     */
    function reclaim(uint256 tournamentId) external {
        if (winners[tournamentId].amount == 0) revert NoWinner();
        if (winners[tournamentId].claimed || winners[tournamentId].reclaimed) revert AlreadyClaimed();

        if (block.timestamp < winners[tournamentId].timestamp + EXPIRATION) revert NotExpired();

        winners[tournamentId].reclaimed = true;

        uint256 totalAmount = winners[tournamentId].amount;
        uint256 amountToTournament = totalAmount * 90 / 100;
        uint256 amountToOwner = totalAmount - amountToTournament;

        (bool success,) = payable(APE_BOMB_CONTRACT_ADDRESS).call{value: amountToTournament}("");
        if (!success) revert TransferFailed();

        (success,) = payable(IApeBomb(APE_BOMB_CONTRACT_ADDRESS).owner()).call{value: amountToOwner}("");
        if (!success) revert TransferFailed();

        emit Reclaimed(tournamentId, APE_BOMB_CONTRACT_ADDRESS, winners[tournamentId].amount);
    }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):