Contract Reference
Factory Source

STPv1 Factory Source

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import "@openzeppelin/contracts/proxy/Clones.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";
import "./SubscriptionTokenV1.sol";
import "./Shared.sol";
 * @title Fabric Subscription Token Factory Contract
 * @author Fabric Inc.
 * @dev A factory which leverages Clones to deploy Fabric Subscription Token Contracts
contract SubscriptionTokenV1Factory is Ownable2Step {
    /// @dev The maximum fee that can be charged for a subscription contract
    uint16 private constant _MAX_FEE_BIPS = 1250;
    /// @dev The number of reward halvings
    uint8 private constant _DEFAULT_REWARD_HALVINGS = 6;
    /// @dev Guard to ensure the deploy fee is met
    modifier feeRequired() {
        require(msg.value >= _feeDeployMin, "Insufficient ETH to deploy");
    /// @dev Emitted upon a successful contract deployment
    event Deployment(address indexed deployment, uint256 feeId);
    /// @dev Emitted when a new fee is created
    event FeeCreated(uint256 indexed id, address collector, uint16 bips);
    /// @dev Emitted when a fee is destroyed
    event FeeDestroyed(uint256 indexed id);
    /// @dev Emitted when the deployment fee changes
    event DeployFeeChange(uint256 amount);
    /// @dev Emitted when the deploy fees are collected by the owner
    event DeployFeeTransfer(address indexed recipient, uint256 amount);
    /// @dev The campaign contract implementation address
    address immutable _implementation;
    /// @dev Fee configuration for agreements and revshare
    struct FeeConfig {
        address collector;
        uint16 basisPoints;
    /// @dev Configured fee ids and their config
    mapping(uint256 => FeeConfig) private _feeConfigs;
    /// @dev Fee to collect upon deployment
    uint256 private _feeDeployMin;
     * @param implementation the SubscriptionTokenV1 implementation address
    constructor(address implementation) Ownable2Step() {
        _implementation = implementation;
        _feeDeployMin = 0;
     * @notice Deploy a new Clone of a SubscriptionTokenV1 contract
     * @param name the name of the collection
     * @param symbol the symbol of the collection
     * @param contractURI the metadata URI for the collection
     * @param tokenURI the metadata URI for the tokens
     * @param tokensPerSecond the number of base tokens required for a single second of time
     * @param minimumPurchaseSeconds the minimum number of seconds an account can purchase
     * @param rewardBps the basis points for reward allocations
     * @param erc20TokenAddr the address of the ERC20 token used for purchases, or the 0x0 for native
     * @param feeConfigId the fee configuration id to use for this deployment (if the id is invalid, the default fee is used)
    function deploySubscription(
        string memory name,
        string memory symbol,
        string memory contractURI,
        string memory tokenURI,
        uint256 tokensPerSecond,
        uint256 minimumPurchaseSeconds,
        uint16 rewardBps,
        address erc20TokenAddr,
        uint256 feeConfigId
    ) public payable feeRequired returns (address) {
        // If an invalid fee id is provided, use the default fee (0)
        FeeConfig memory fees = _feeConfigs[feeConfigId];
        if (feeConfigId != 0 && fees.collector == address(0)) {
            fees = _feeConfigs[0];
        address deployment = Clones.clone(_implementation);
        emit Deployment(deployment, feeConfigId);
        return deployment;
     * @dev Owner Only: Transfer accumulated fees
     * @param recipient the address to transfer the fees to
    function transferDeployFees(address recipient) external onlyOwner {
        uint256 amount = address(this).balance;
        require(amount > 0, "No fees to collect");
        emit DeployFeeTransfer(recipient, amount);
        (bool sent,) = payable(recipient).call{value: amount}("");
        require(sent, "Failed to transfer Ether");
     * @notice Create a fee for future deployments using that fee id
     * @param id the id of the fee for future deployments
     * @param collector the address of the fee collector
     * @param bips the fee in basis points, allocated during withdraw
    function createFee(uint256 id, address collector, uint16 bips) external onlyOwner {
        require(bips <= _MAX_FEE_BIPS, "Fee exceeds maximum");
        require(bips > 0, "Fee cannot be 0");
        require(collector != address(0), "Collector cannot be 0x0");
        require(_feeConfigs[id].collector == address(0), "Fee exists");
        _feeConfigs[id] = FeeConfig(collector, bips);
        emit FeeCreated(id, collector, bips);
     * @notice Destroy a fee schedule
     * @param id the id of the fee to destroy
    function destroyFee(uint256 id) external onlyOwner {
        require(_feeConfigs[id].collector != address(0), "Fee does not exists");
        emit FeeDestroyed(id);
        delete _feeConfigs[id];
     * @notice Update the deploy fee (wei)
     * @param minFeeAmount the amount of wei required to deploy a campaign
    function updateMinimumDeployFee(uint256 minFeeAmount) external onlyOwner {
        _feeDeployMin = minFeeAmount;
        emit DeployFeeChange(minFeeAmount);
     * @notice Fetch the fee schedule for a given fee id
     * @return collector the address of the fee collector, or the 0 address if no fees are collected
     * @return bips the fee in basis points, allocated during withdraw
     * @return deployFeeWei the amount of wei required to deploy a campaign
    function feeInfo(uint256 feeId) external view returns (address collector, uint16 bips, uint256 deployFeeWei) {
        FeeConfig memory fees = _feeConfigs[feeId];
        return (fees.collector, fees.basisPoints, _feeDeployMin);