Factory.sol 

 

The Factory.sol contract is the central management contract of Xenona. It deploys and oversees individual giveaways, manages LINK tokens for randomness requests, handles platform fees, and provides emergency controls. 

 


 

Overview 

 

Factory.sol coordinates all Xenona giveaways. While each giveaway is a separate Giveaway.sol contract, the factory ensures: 

 

  • Authorized giveaway creation via owner signatures
  • Support for native tokens and ERC-20 prizes
  • Automated LINK allocation for Chainlink VRF
  • Lifecycle management including ending, canceling, and refunds
  • Owner-controlled token and fee management 

 

This guarantees secure, compliant, and fair operations across all giveaways. 

 


 

Main Responsibilities 

 

  • Creating Giveaways: Ensures unique IDs, verifies owner signatures, securely funds the prize, and emits a NewGiveaway event.
  • Ending Giveaways: Verifies readiness, allocates LINK for VRF, triggers winner selection, restricted to factory owner.
  • Canceling Giveaways: Refunds prizes if a giveaway cannot proceed, providing an emergency mechanism.
  • Managing Tokens and Fees: Transfers native or ERC-20 tokens for fees, refunds, or recovery, controlled by the factory owner. 

 


 

Key Components 

 

  • Owner (_owner): manages sensitive functions
  • LINK Token (_linkToken): funds Chainlink VRF requests
  • VRF Wrapper (_vrfWrapper): interface for randomness
  • Giveaway Map (_giveawaysMap): tracks all deployed giveaways 

 


 

Security 

 

  • Only factory owner can create, end, or cancel giveaways
  • SafeERC20 ensures secure token transfers
  • Prizes are immediately sent to giveaway contracts
  • LINK allocation is strictly controlled for VRF requests 

 


 

Contract Source Code 

 

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.19;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {Giveaway} from "src/Giveaway.sol";

/**
 * @notice Factory contract for deploying and managing verifiably fair giveaway contracts
 * @dev Integrates with Chainlink VRF v2.5 for provably random winner selection. The factory
 *      maintains ownership control over all giveaway operations and manages LINK token allocation
 *      for VRF requests.
 *
 * Key Features:
 * - Signature-authorized giveaway creation to prevent unauthorized deployments
 * - Support for both native and ERC20 tokens
 * - Automated VRF cost calculation and LINK token distribution
 * - Centralized giveaway lifecycle management
 * - Owner-controlled fund recovery mechanisms
 */
contract Factory {
    using SafeERC20 for ERC20;

    /*//////////////////////////////////////////////////////////////
                                STORAGE
    //////////////////////////////////////////////////////////////*/

    /// Address of the factory owner with administrative privileges
    address private _owner;

    /// Address of the Chainlink LINK token contract used for VRF payments
    address private _linkToken;

    /// Address of the Chainlink VRF v2.5 Wrapper contract for randomness requests
    address private _vrfWrapper;

    /// Mapping tracking all created giveaway IDs to prevent duplicates
    mapping(uint64 => bool) private _giveawaysMap;

    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    /// Emitted when a new giveaway contract is successfully deployed
    event NewGiveaway(uint64 giveawayId, address giveawayAddress, uint256 endTime);

    /*//////////////////////////////////////////////////////////////
                               MODIFIERS
    //////////////////////////////////////////////////////////////*/

    /// Restricts function access to the factory owner only
    modifier onlyOwner() {
        _onlyOwner();
        _;
    }

    /// Validates that the caller is the factory owner
    function _onlyOwner() internal view {
        require(msg.sender == _owner, "Only factory owner can call");
    }

    /*//////////////////////////////////////////////////////////////
                              CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Initializes the Factory contract with required Chainlink components
     * @dev The deployer automatically becomes the factory owner
     */
    constructor(address linkToken, address vrfWrapper) {
        _owner = msg.sender;
        _linkToken = linkToken;
        _vrfWrapper = vrfWrapper;
    }

    /// Enables the factory to receive native tokens
    receive() external payable {}

    /*//////////////////////////////////////////////////////////////
                            EXTERNAL FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Creates and deploys a new Giveaway contract with specified parameters
     * @dev Performs the following operations:
     * - Verifies owner signature to authorize deployment
     * - Ensures giveaway ID uniqueness
     * - Deploys new Giveaway contract with appropriate prize funding
     * - Emits event for off-chain tracking
     *
     * @param giveawayId Unique identifier for this giveaway
     * @param prizeToken Address of prize token (address(0) for native token)
     * @param prizeAmount Amount of prize to award
     * @param duration Time period in seconds for participation
     * @param minParticipants Minimum participants required
     * @param maxParticipants Maximum participants allowed
     * @param signature Owner's authorization signature
     *
     * Requirements:
     * - Signature must be from factory owner and match all parameters
     * - Giveaway ID must not have been used previously
     * - For native token prizes: msg.value must be at least prizeAmount
     * - For ERC20 prizes: caller must have approved factory to spend prizeAmount
     */
    function createGiveaway(
        uint64 giveawayId,
        address prizeToken,
        uint256 prizeAmount,
        uint64 duration,
        uint32 minParticipants,
        uint32 maxParticipants,
        bytes calldata signature
    ) external payable {
        // Validate owner signature to authorize creation
        bytes32 messageHash = keccak256(
            abi.encodePacked(
                address(this), giveawayId, prizeToken, prizeAmount, duration, minParticipants, maxParticipants
            )
        );

        address signer = ECDSA.recover(messageHash, signature);
        require(signer == _owner, "Invalid owner signature");

        // Ensure giveaway ID hasn't been used before
        require(!_giveawaysMap[giveawayId], "Giveaway already created");
        _giveawaysMap[giveawayId] = true;

        // Deploy giveaway contract
        Giveaway giveaway;

        if (prizeToken == address(0)) {
            // Deploy with native token prize
            require(msg.value >= prizeAmount, "Not enough ETH sent");

            giveaway = new Giveaway{value: msg.value}(
                giveawayId,
                msg.sender,
                prizeToken,
                prizeAmount,
                duration,
                minParticipants,
                maxParticipants,
                _owner,
                address(this),
                _linkToken,
                _vrfWrapper
            );
        } else {
            // Deploy with ERC20 prize
            giveaway = new Giveaway(
                giveawayId,
                msg.sender,
                prizeToken,
                prizeAmount,
                duration,
                minParticipants,
                maxParticipants,
                _owner,
                address(this),
                _linkToken,
                _vrfWrapper
            );

            ERC20(prizeToken).safeTransferFrom(msg.sender, address(giveaway), prizeAmount);
        }

        // Emit creation event
        uint256 endTime = block.timestamp + duration;
        emit NewGiveaway(giveawayId, address(giveaway), endTime);
    }

    /**
     * @notice Ends a giveaway by requesting random number from Chainlink VRF
     * @dev Orchestrates the completion process:
     * - Verifies giveaway is ready to end
     * - Calculates required LINK tokens for VRF request
     * - Transfers LINK to giveaway contract for VRF payment
     * - Triggers VRF request which will callback with random winner selection
     *
     * @param giveawayAddress Address of the deployed Giveaway contract to end
     * @param vrfCallbackGasLimit Gas limit for VRF callback execution
     *
     * Requirements:
     * - Caller must be factory owner
     * - Giveaway must be in valid ending state
     * - Factory must have sufficient LINK balance for VRF request
     */
    function endGiveaway(address giveawayAddress, uint32 vrfCallbackGasLimit) external onlyOwner {
        Giveaway giveaway = Giveaway(giveawayAddress);

        // Verify giveaway meets ending conditions
        (bool readyToEnd, string memory state) = giveaway.checkReadyToEnd();
        require(readyToEnd, state);

        // Calculate LINK tokens needed for VRF request
        uint256 vrfRequestPrice = giveaway.i_vrfV2PlusWrapper().calculateRequestPrice(vrfCallbackGasLimit, 1);

        // Ensure factory has enough LINK to cover request
        uint256 linkBalance = ERC20(_linkToken).balanceOf(address(this));
        require(linkBalance >= vrfRequestPrice, "LINK balance is too low");

        // Transfer LINK to giveaway and initiate ending process
        ERC20(_linkToken).safeTransfer(giveawayAddress, vrfRequestPrice);
        giveaway.end(vrfCallbackGasLimit, vrfRequestPrice);
    }

    /**
     * @notice Cancels a giveaway and refunds the prize to the creator
     * @dev Provides emergency mechanism to cancel problematic giveaways and refund prizes
     *
     * @param giveawayAddress Address of the Giveaway contract to cancel
     *
     * Requirements:
     * - Caller must be factory owner
     * - Giveaway must not have already been ended
     */
    function cancelGiveaway(address giveawayAddress) external onlyOwner {
        Giveaway giveaway = Giveaway(giveawayAddress);
        giveaway.cancel();
    }

    /**
     * @notice Withdraws tokens from the factory to specified receiver
     * @dev Enables recovery of excess LINK tokens, withdraw of platform fees, or accidentally sent tokens
     *
     * @param token Address of token to withdraw (address(0) for native token)
     * @param receiver Address that will receive the withdrawn funds
     * @param amount Amount of tokens to withdraw in wei/smallest unit
     *
     * Requirements:
     * - Caller must be factory owner
     * - Factory must have sufficient balance of specified token
     */
    function transfer(address token, address receiver, uint256 amount) external onlyOwner {
        if (amount == 0) return;

        if (token == address(0)) {
            // Transfer native token
            (bool success,) = receiver.call{value: amount}("");
            require(success, "Transfer failed");
        } else {
            // Transfer ERC20 token
            ERC20(token).safeTransfer(receiver, amount);
        }
    }
}

 

Factory.sol serves as the backbone of Xenona, ensuring secure, automated, and fair deployment and management of all giveaways while maintaining centralized oversight.