Cryo Explorer Ethereum Mainnet

Address Contract Partially Verified

Address 0x5A96C8981CAbad76D54af9DCe4E2A7189c5a4c6C
Balance 0 ETH
Nonce 1
Code Size 3990 bytes
Indexed Transactions 0
External Etherscan · Sourcify

Contract Bytecode

3990 bytes
0x6080806040526004361015610012575f80fd5b5f3560e01c9081630d0e96da146108b757508063158ef93e1461089557806318d052e3146105875780634cd620981461051a5780635ce97dbb146104e05780636588103b1461049c5780636d7779d9146104585780638129fc1c146103ea578063830953ab146103cd5780639e34070f146102a2578063cd52d7c9146102d1578063ce516507146102a2578063d1a26ed314610285578063d54ad2a114610268578063da25de3c146101bd578063ddd5e1b214610197578063e6798baa1461017b578063e83e2081146101355763fc0c546a146100ed575f80fd5b34610131575f366003190112610131576040517f0000000000000000000000006b419b281491473da2948b23fcb42f80e4ef25296001600160a01b03168152602090f35b5f80fd5b346101315760603660031901126101315761014e6108d1565b604435906001600160a01b03821682036101315760209161017191600435610a1b565b6040519015158152f35b34610131575f3660031901126101315760205f54604051908152f35b34610131576040366003190112610131576101bb6101b36108d1565b600435610c54565b005b34610131575f366003190112610131576040516370a0823160e01b81523060048201526020816024817f0000000000000000000000006b419b281491473da2948b23fcb42f80e4ef25296001600160a01b03165afa801561025d575f9061022a575b602090604051908152f35b506020813d602011610255575b81610244602093836108fd565b81010312610131576020905161021f565b3d9150610237565b6040513d5f823e3d90fd5b34610131575f366003190112610131576020600554604051908152f35b34610131575f366003190112610131576020600154604051908152f35b34610131576020366003190112610131576004355f526006602052602060ff60405f2054166040519015158152f35b34610131576102df366108e7565b9060ff600454166103bf577f00000000000000000000000097a294a099e8bcccf5adbdcf8050f186d67968e96001600160a01b031633036103b057808211156103a157805f55816001558082039180831161038d578260025514610379577f00000000000000000000000000000000000000000052b7d2dcc80cd2e400000004806003551561036a57005b63797fe85f60e11b5f5260045ffd5b634e487b7160e01b5f52601260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b63561ce9bb60e01b5f5260045ffd5b63ea8e4eb560e01b5f5260045ffd5b62dc149f60e41b5f5260045ffd5b34610131575f366003190112610131576020600354604051908152f35b34610131575f3660031901126101315760045460ff81166103bf577f00000000000000000000000097a294a099e8bcccf5adbdcf8050f186d67968e96001600160a01b031633036103b057600254156104495760ff1916600117600455005b63ff67cee560e01b5f5260045ffd5b34610131575f366003190112610131576040517f00000000000000000000000097a294a099e8bcccf5adbdcf8050f186d67968e96001600160a01b03168152602090f35b34610131575f366003190112610131576040517f000000000000000000000000282bdd42f4eb70e7a9d9f40c8fea0825b7f68c5d6001600160a01b03168152602090f35b34610131575f3660031901126101315760206040517f00000000000000000000000000000000000000000052b7d2dcc80cd2e40000008152f35b3461013157610528366108e7565b5f915f5481106103a15760015482116103a157818110156103a1575b81811061055657602083604051908152f35b805f52600660205260ff60405f205416610573575b600101610544565b9161057f60019161097d565b92905061056b565b346101315760403660031901126101315760043567ffffffffffffffff8111610131573660238201121561013157806004013567ffffffffffffffff8111610131573660248260051b84010111610131576105e06108d1565b905f60ff60045416156108865781156108775790915f906106008461094b565b6001600160a01b037f000000000000000000000000282bdd42f4eb70e7a9d9f40c8fea0825b7f68c5d811696908316801515955f959392905b888710156107bc5760248760051b830101355f54811080156107b0575b6106d957805f52600660205260ff60405f2054166106d9573389806107a6575b610727575b60249060208d604051938480926331a9108f60e11b82528760048301525afa91821561025d575f926106f7575b506001600160a01b039081169116036106d9576001916106cb886106d093610c54565b61097d565b965b0195610639565b96600191976106f16106ea8861097d565b978761098b565b526106d2565b61071991925060203d8111610720575b61071181836108fd565b8101906109fc565b908d6106a8565b503d610707565b5061074c6020828d6040519384928392632e7cda1d60e21b84528d33600486016109cb565b03816c447e69651d841bd8d104bed4935afa90811561025d575f91610778575b50156106d9578661067b565b610799915060203d811161079f575b61079181836108fd565b8101906109b3565b8c61076c565b503d610787565b5033851415610676565b50600154811015610656565b929390506107c98161094b565b935f5b8281106108525750506040519083825260208201527f825d262cdc35d64b4acb49b9d7488315b54f08fcf7e7c3fafaac63cd0f12aaf060403392a3604051918291604083019083526040602084015281518091526020606084019201905f5b818110610839575050500390f35b825184528594506020938401939092019160010161082b565b806108626001928498979861098b565b5161086d828861098b565b52019493946107cc565b63c2e5347d60e01b5f5260045ffd5b6321c4e35760e21b5f5260045ffd5b34610131575f36600319011261013157602060ff600454166040519015158152f35b34610131575f366003190112610131576020906002548152f35b602435906001600160a01b038216820361013157565b6040906003190112610131576004359060243590565b90601f8019910116810190811067ffffffffffffffff82111761091f57604052565b634e487b7160e01b5f52604160045260245ffd5b67ffffffffffffffff811161091f5760051b60200190565b9061095582610933565b61096260405191826108fd565b8281528092610973601f1991610933565b0190602036910137565b5f19811461038d5760010190565b805182101561099f5760209160051b010190565b634e487b7160e01b5f52603260045260245ffd5b90816020910312610131575180151581036101315790565b6001600160a01b0391821681529181166020830152909116604082015260608101919091525f608082015260a00190565b9081602091031261013157516001600160a01b03811681036101315790565b9160ff6004541615610c41575f5483108015610c48575b610c4157825f52600660205260ff60405f205416610c415782826001600160a01b0383168015159081610c2d575b50610b89575b50506040516331a9108f60e11b81526004810193909352506020826024817f000000000000000000000000282bdd42f4eb70e7a9d9f40c8fea0825b7f68c5d6001600160a01b03165afa91821561025d575f92610b68575b506001600160a01b03908116911603610b32576040516370a0823160e01b81523060048201526020816024817f0000000000000000000000006b419b281491473da2948b23fcb42f80e4ef25296001600160a01b03165afa90811561025d575f91610b36575b5060035411610b3257600190565b5f90565b90506020813d602011610b60575b81610b51602093836108fd565b8101031261013157515f610b24565b3d9150610b44565b610b8291925060203d6020116107205761071181836108fd565b905f610abe565b604051632e7cda1d60e21b815292935060209183918291610bda917f000000000000000000000000282bdd42f4eb70e7a9d9f40c8fea0825b7f68c5d6001600160a01b0316908890600486016109cb565b03816c447e69651d841bd8d104bed4935afa90811561025d575f91610c0e575b5015610c08575f8281610a66565b50505f90565b610c27915060203d60201161079f5761079181836108fd565b5f610bfa565b6001600160a01b038316141590505f610a60565b5050505f90565b50600154831015610a32565b60ff6004541615610886575f5481108015610f54575b610f4557805f52600660205260ff60405f205416610f36576001600160a01b03821691339083151580610f2c575b610e94575b506040516331a9108f60e11b815260048101839052906020826024817f000000000000000000000000282bdd42f4eb70e7a9d9f40c8fea0825b7f68c5d6001600160a01b03165afa91821561025d575f92610e73575b506001600160a01b03908116911603610e64576040516370a0823160e01b81523060048201527f0000000000000000000000006b419b281491473da2948b23fcb42f80e4ef25296001600160a01b031690602081602481855afa90811561025d575f91610e32575b5060035411610e235760205f918383526006825260408320600160ff19825416179055610d8960055461097d565b6005556044600354604051948593849263a9059cbb60e01b845233600485015260248401525af190811561025d575f91610e04575b5015610df5576003546040519081527fca8bf70624ec0ecfc925e5746a0e4625afe01129043c1c7201c7ce01075ea3ac60203392a4565b6312171d8360e31b5f5260045ffd5b610e1d915060203d60201161079f5761079181836108fd565b5f610dbe565b631e9acf1760e31b5f5260045ffd5b90506020813d602011610e5c575b81610e4d602093836108fd565b8101031261013157515f610d5b565b3d9150610e40565b631022318760e21b5f5260045ffd5b610e8d91925060203d6020116107205761071181836108fd565b905f610cf3565b604051632e7cda1d60e21b815290915060208180610ee1867f000000000000000000000000282bdd42f4eb70e7a9d9f40c8fea0825b7f68c5d6001600160a01b03168733600486016109cb565b03816c447e69651d841bd8d104bed4935afa90811561025d575f91610f0d575b50156103b0575f610c9d565b610f26915060203d60201161079f5761079181836108fd565b5f610f01565b5033841415610c98565b630c8d9eab60e31b5f5260045ffd5b635f6f1f0b60e01b5f5260045ffd5b50600154811015610c6a56fea26469706673582212209928deb161e4e81c1e9c82f92647672404a712fe1d2e81e23d90acab9ef724cf64736f6c634300081e0033

Verified Source Code Partial Match

Compiler: v0.8.30+commit.73712a01 EVM: prague Optimization: Yes (200 runs)
StrategicDrop.sol 332 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

/**
 * @title StrategicDrop
 * @notice Strategic Reserve airdrop contract for any collection
 * @dev Allows NFT holder to claim an a share of deposited tokens once per NFT
 */

interface IERC20 {
    function transfer(address recipient, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

interface IERC721 {
    function ownerOf(uint256 tokenId) external view returns (address);
}

interface IDelegateRegistry {
    function checkDelegateForERC721(
        address to,        // The delegate wallet
        address from,      // The NFT owner's wallet
        address contract_, // The NFT collection address
        uint256 tokenId,   // Specific NFT ID being checked
        bytes32 rights     // Subdelegation rights (use "" for all)
    ) external view returns (bool);
}

/* ═══════════════════════════════════════════════════ */
/*                    CUSTOM ERRORS                    */
/* ═══════════════════════════════════════════════════ */
/// @notice Invalid address (zero address)
error InvalidAddress();
/// @notice Zero amount provided
error ZeroAmount();
/// @notice Contract already initialized
error AlreadyInitialized();
/// @notice Contract not initialized yet
error NotInitialized();
/// @notice Caller not authorized
error NotAuthorized();
/// @notice Token ID ranges not set
error RangesNotSet();
/// @notice Invalid token ID range
error InvalidRange();
/// @notice Invalid NFT ID for claim
error InvalidNFTId();
/// @notice Claim amount rounds to zero
error ClaimAmountRoundsToZero();
/// @notice NFT already claimed airdrop
error AlreadyClaimed();
/// @notice Caller not NFT owner
error NotNFTOwner();
/// @notice Insufficient contract balance
error InsufficientBalance();
/// @notice Token transfer failed
error TransferFailed();
/// @notice Empty NFT ID array provided
error EmptyBatch();

contract StrategicDrop {
    //
    //    _____ __             __             _
    //   / ___// /__________ _/ /____  ____ _(_)____
    //   \__ \/ __/ ___/ __ `/ __/ _ \/ __ `/ / ___/
    //  ___/ / /_/ /  / /_/ / /_/  __/ /_/ / / /__
    // /____/\__/_/   \__,_/\__/\___/\__, /_/\___/
    //    / __ \___  ________  _____/____/___
    //   / /_/ / _ \/ ___/ _ \/ ___/ | / / _ \
    //  / _, _/  __(__  )  __/ /   | |/ /  __/
    // /_/ |_|\___/____/\___/_/    |___/\___/

    address constant DELEGATE_REGISTRY = 0x00000000000000447e69651d841bD8D104Bed493;

    IERC20 public immutable token;           // The ERC20 token being airdropped
    IERC721 public immutable nftCollection;  // The NFT collection eligible for claims
    uint256 public immutable totalAirdrop;   // Total tokens allocated for airdrop
    address public immutable authorizedInitializer; // Address authorized to initialize ranges

    uint256 public startTokenId;   // First valid token ID (set during initialization)
    uint256 public endTokenId;     // Last valid token ID + 1 (set during initialization)
    uint256 public totalNFTs;      // Total count of NFTs (calculated during initialization)
    uint256 public claimAmount;    // Amount each NFT can claim (calculated during initialization)
    bool public initialized;       // Whether the airdrop has been initialized
    uint256 public totalClaimed;   // Total number of claims made

    // Track which NFT IDs have claimed
    mapping(uint256 => bool) public hasClaimed;

    // Events
    event Claimed(address indexed claimer, uint256 indexed nftId, uint256 amount, address indexed nftOwner);
    event BatchClaimed(address indexed claimer, uint256 successCount, uint256 failCount, address indexed nftOwner);

    /// @notice Constructor - sets immutable values (ranges set later via initialize)
    /// @param _token Address of the ERC20 token to distribute
    /// @param _nftCollection Address of the NFT collection
    /// @param _totalAirdrop Total amount of tokens to distribute across all NFTs
    /// @param _authorizedInitializer Address authorized to call initialize()
    constructor(
        address _token,
        address _nftCollection,
        uint256 _totalAirdrop,
        address _authorizedInitializer
    ) {
        if (_token == address(0)) revert InvalidAddress();
        if (_nftCollection == address(0)) revert InvalidAddress();
        if (_totalAirdrop == 0) revert ZeroAmount();
        if (_authorizedInitializer == address(0)) revert InvalidAddress();

        token = IERC20(_token);
        nftCollection = IERC721(_nftCollection);
        totalAirdrop = _totalAirdrop;
        authorizedInitializer = _authorizedInitializer;
    }

    /// @notice Finalize and enable claims
    /// @dev Locks token ID ranges and enables claiming
    function initialize() external {
        if (initialized) revert AlreadyInitialized();
        if (msg.sender != authorizedInitializer) revert NotAuthorized();
        if (totalNFTs == 0) revert RangesNotSet();

        initialized = true;
    }

    /// @notice Set or update the token ID ranges for the airdrop
    /// @param _startTokenId First valid token ID in the collection
    /// @param _endTokenId Last valid token ID + 1 (exclusive)
    /// @dev Can be called multiple times before initialization
    function setTokenIdRange(uint256 _startTokenId, uint256 _endTokenId) external {
        if (initialized) revert AlreadyInitialized();
        if (msg.sender != authorizedInitializer) revert NotAuthorized();
        if (_endTokenId <= _startTokenId) revert InvalidRange();

        startTokenId = _startTokenId;
        endTokenId = _endTokenId;
        totalNFTs = _endTokenId - _startTokenId;
        claimAmount = totalAirdrop / totalNFTs;

        if (claimAmount == 0) revert ClaimAmountRoundsToZero();
    }

    /// @notice Internal claim logic - validates and processes a single claim
    /// @param _nftId The ID of the NFT to claim for
    /// @param _nftOwner The address that owns the NFT (use address(0) if caller is owner)
    function _claim(uint256 _nftId, address _nftOwner) internal {
        // Check 0: Airdrop must be initialized
        if (!initialized) revert NotInitialized();

        // Check 1: NFT ID must be within valid range
        if (_nftId < startTokenId || _nftId >= endTokenId) revert InvalidNFTId();

        // Check 2: NFT must not have already claimed
        if (hasClaimed[_nftId]) revert AlreadyClaimed();

        // Resolve actual NFT owner (supports delegation)
        address requester = msg.sender;
        if (_nftOwner != address(0) && _nftOwner != msg.sender) {
            bool isDelegateValid = IDelegateRegistry(DELEGATE_REGISTRY)
                .checkDelegateForERC721(
                    msg.sender,              // The delegate calling this function
                    _nftOwner,               // The actual NFT owner
                    address(nftCollection),  // The NFT collection being checked
                    _nftId,                  // Specific NFT ID being claimed
                    ""                       // Empty string = accept any delegation rights
                );
            if (!isDelegateValid) revert NotAuthorized();
            requester = _nftOwner;  // Use NFT owner as the actual owner for all checks
        }

        // Check 3: Requester must own the NFT
        if (nftCollection.ownerOf(_nftId) != requester) revert NotNFTOwner();

        // Check 4: Contract must have sufficient balance
        if (token.balanceOf(address(this)) < claimAmount) revert InsufficientBalance();

        // Effects: Mark as claimed BEFORE transfer (reentrancy protection)
        hasClaimed[_nftId] = true;
        totalClaimed++;

        // Interaction: Transfer tokens to claimer (delegate)
        if (!token.transfer(msg.sender, claimAmount)) revert TransferFailed();

        // Emit event
        emit Claimed(msg.sender, _nftId, claimAmount, _nftOwner);
    }

    /// @notice Allows the owner of an NFT (or their delegate) to claim airdrop allocation
    /// @param _nftId The ID of the NFT to claim for
    /// @param _nftOwner The address that owns the NFT (use address(0) if caller is owner)
    function claim(uint256 _nftId, address _nftOwner) external {
        _claim(_nftId, _nftOwner);
    }

    /// @notice Claim airdrop for multiple NFTs in a single transaction
    /// @param _nftIds Array of NFT IDs to claim for
    /// @param _nftOwner The address that owns the NFTs (use address(0) if caller is owner)
    /// @return successCount Number of successful claims
    /// @return failedIds Array of NFT IDs that failed to claim
    /// @dev Validates each NFT before claiming. Skips invalid NFTs. Critical errors revert entire batch.
    function claimBatch(uint256[] calldata _nftIds, address _nftOwner)
        external
        returns (uint256 successCount, uint256[] memory failedIds)
    {
        // Validate batch input
        if (!initialized) revert NotInitialized();
        if (_nftIds.length == 0) revert EmptyBatch();

        // Track failures
        uint256 failCount = 0;
        uint256[] memory tempFailedIds = new uint256[](_nftIds.length);

        // Process each NFT with explicit pre-validation
        for (uint256 i = 0; i < _nftIds.length; i++) {
            uint256 nftId = _nftIds[i];

            // Skip if out of valid range
            if (nftId < startTokenId || nftId >= endTokenId) {
                tempFailedIds[failCount++] = nftId;
                continue;
            }

            // Skip if already claimed
            if (hasClaimed[nftId]) {
                tempFailedIds[failCount++] = nftId;
                continue;
            }

            // Resolve delegation and validate authorization
            address requester = msg.sender;
            if (_nftOwner != address(0) && _nftOwner != msg.sender) {
                bool isDelegateValid = IDelegateRegistry(DELEGATE_REGISTRY)
                    .checkDelegateForERC721(
                        msg.sender,              // The delegate calling this function
                        _nftOwner,               // The actual NFT owner
                        address(nftCollection),  // The NFT collection being checked
                        nftId,                   // Specific NFT ID being claimed
                        ""                       // Empty string = accept any delegation rights
                    );
                if (!isDelegateValid) {
                    tempFailedIds[failCount++] = nftId;
                    continue;
                }
                requester = _nftOwner;
            }

            // Skip if requester doesn't own the NFT
            if (nftCollection.ownerOf(nftId) != requester) {
                tempFailedIds[failCount++] = nftId;
                continue;
            }

            // All pre-checks passed - call internal claim logic
            // Critical errors (InsufficientBalance, TransferFailed) will revert entire batch
            _claim(nftId, _nftOwner);
            successCount++;
        }

        // Build right-sized result array
        failedIds = new uint256[](failCount);
        for (uint256 i = 0; i < failCount; i++) {
            failedIds[i] = tempFailedIds[i];
        }

        // Emit batch summary event
        emit BatchClaimed(msg.sender, successCount, failCount, _nftOwner);
    }

    /// @notice Check if an NFT has claimed its allocation
    /// @param _nftId The NFT ID to check
    /// @return bool True if claimed, false otherwise
    function isClaimed(uint256 _nftId) external view returns (bool) {
        return hasClaimed[_nftId];
    }

    /// @notice Get the current balance of airdrop tokens in the contract
    /// @return uint256 The token balance
    function remainingBalance() external view returns (uint256) {
        return token.balanceOf(address(this));
    }

    /// @notice Calculate how many claims have been made
    /// @param _startId Starting NFT ID to check (inclusive)
    /// @param _endId Ending NFT ID to check (exclusive)
    /// @return count Number of claims in the range
    /// @dev Use this to count claims in batches to avoid gas limits
    function countClaims(uint256 _startId, uint256 _endId) external view returns (uint256 count) {
        if (_startId < startTokenId) revert InvalidRange();
        if (_endId > endTokenId) revert InvalidRange();
        if (_startId >= _endId) revert InvalidRange();

        for (uint256 i = _startId; i < _endId; i++) {
            if (hasClaimed[i]) {
                count++;
            }
        }
    }

    /// @notice Check if a wallet can claim for a specific NFT (direct ownership or delegation)
    /// @param _nftId The NFT ID to check
    /// @param _claimer The wallet attempting to claim
    /// @param _nftOwner The address that owns the NFT (use address(0) if claimer is owner)
    /// @return canClaimNow True if claim is possible, false otherwise
    function canClaim(uint256 _nftId, address _claimer, address _nftOwner) external view returns (bool canClaimNow) {
        // Must be initialized
        if (!initialized) return false;

        // Must be in valid range
        if (_nftId < startTokenId || _nftId >= endTokenId) return false;

        // Must not have claimed already
        if (hasClaimed[_nftId]) return false;

        // Resolve requester
        address requester = _claimer;
        if (_nftOwner != address(0) && _nftOwner != _claimer) {
            // Check delegation
            bool isDelegateValid = IDelegateRegistry(DELEGATE_REGISTRY)
                .checkDelegateForERC721(_claimer, _nftOwner, address(nftCollection), _nftId, "");
            if (!isDelegateValid) return false;
            requester = _nftOwner;
        }

        // Check ownership
        if (nftCollection.ownerOf(_nftId) != requester) return false;

        // Check balance
        if (token.balanceOf(address(this)) < claimAmount) return false;

        return true;
    }
}

Read Contract

authorizedInitializer 0x6d7779d9 → address
canClaim 0xe83e2081 → bool
claimAmount 0x830953ab → uint256
countClaims 0x4cd62098 → uint256
endTokenId 0xd1a26ed3 → uint256
hasClaimed 0xce516507 → bool
initialized 0x158ef93e → bool
isClaimed 0x9e34070f → bool
nftCollection 0x6588103b → address
remainingBalance 0xda25de3c → uint256
startTokenId 0xe6798baa → uint256
token 0xfc0c546a → address
totalAirdrop 0x5ce97dbb → uint256
totalClaimed 0xd54ad2a1 → uint256
totalNFTs 0x0d0e96da → uint256

Write Contract 4 functions

These functions modify contract state and require a wallet transaction to execute.

claim 0xddd5e1b2
uint256 _nftId
address _nftOwner
claimBatch 0x18d052e3
uint256[] _nftIds
address _nftOwner
returns: uint256, uint256[]
initialize 0x8129fc1c
No parameters
setTokenIdRange 0xcd52d7c9
uint256 _startTokenId
uint256 _endTokenId

Recent Transactions

No transactions found for this address