TokenOpsMerkleDistributorFactory
The factory contract is the entry point for creating new TokenOps Merkle Distributor instances. It provides a standardized way to deploy distributors with configurable fee structures and manages the overall protocol parameters.
Overview
The TokenOpsMerkleDistributorFactory
contract serves as the central hub for:
- Deploying new distributor contracts
- Managing protocol fees and configurations
- Providing standardized interfaces for distributors
- Ensuring consistent behavior across all deployments
Key Features
🏭 Standardized Deployment
- Consistent contract interfaces across all distributors
- Predictable deployment addresses
- Automated configuration management
💰 Flexible Fee Management
- Multiple Fee Types: Gas fees or token-based fees
- Custom Configurations: Per-user fee customization
- Default Settings: Protocol-wide fee defaults
🔧 Configuration Management
- Global Settings: Protocol-wide configurations
- User-specific Settings: Custom fee structures
- Upgradeability: Future-proof design patterns
Contract Details
Core Functions
Deployment Functions
newMerkleDistributor
Creates a new ERC20 token distributor contract.
function newMerkleDistributor(
address tokenAddress,
bytes32 merkleRoot,
uint32 startTime,
uint32 endTime,
ITokenOpsStaking staking,
address rewardOwner,
uint256 bonusPercentage
) external returns (address);
Parameters:
tokenAddress
: The ERC20 token contract addressmerkleRoot
: The Merkle tree root hashstartTime
: When claiming can begin (Unix timestamp)endTime
: When claiming must end (Unix timestamp)staking
: Optional staking contract (useaddress(0)
if none)rewardOwner
: Owner of staking rewards (useaddress(0)
if none)bonusPercentage
: Bonus percentage for staking (0-10000 basis points)
Returns:
address
: The deployed distributor contract address
Example:
const distributorAddress = await factory.newMerkleDistributor(
"0x1234567890123456789012345678901234567890", // USDC token
"0xabcdef...", // Merkle root
1672531200, // Start time: Jan 1, 2023
1675123200, // End time: Jan 31, 2023
"0x0000000000000000000000000000000000000000", // No staking
"0x0000000000000000000000000000000000000000", // No reward owner
0 // No bonus
);
newMerkleDistributorNative
Creates a new native token distributor contract.
function newMerkleDistributorNative(
bytes32 merkleRoot,
uint32 startTime,
uint32 endTime
) external payable returns (address);
Parameters:
merkleRoot
: The Merkle tree root hashstartTime
: When claiming can begin (Unix timestamp)endTime
: When claiming must end (Unix timestamp)
Note: This function is payable
- send the total distribution amount with the call.
Example:
const distributorAddress = await factory.newMerkleDistributorNative(
"0xabcdef...", // Merkle root
1672531200, // Start time
1675123200, // End time
100000000000000000000 // 100 ETH to distribute
);
Fee Management Functions
setFeeCollector
Updates the protocol fee collector address.
function setFeeCollector(address newFeeCollector) external onlyOwner;
setDefaultGasFee
Sets the default gas fee for new distributors.
function setDefaultGasFee(uint256 newGasFee) external onlyOwner;
setDefaultTokenFee
Sets the default token fee percentage.
function setDefaultTokenFee(uint256 newTokenFee) external onlyOwner;
setCustomFee
Configures custom fees for specific users.
function setCustomFee(
address campaignCreator,
ITypes.FeeType feeType,
uint240 newFee
) external onlyOwner;
State Variables
Public Variables
address public feeCollector; // Current fee collector
uint256 public defaultGasFee; // Default gas fee amount
uint256 public defaultTokenFee; // Default token fee percentage
ITypes.FeeType public defaultFeeType; // Default fee type
Fee Configuration
The factory maintains both default and custom fee configurations:
struct CustomFee {
bool enabled; // Whether custom fee is active
ITypes.FeeType preferredFeeType; // Preferred fee type
uint256 gasFee; // Custom gas fee amount
uint256 tokenFee; // Custom token fee percentage
}
Events
Deployment Events
event MerkleDistributorCreated(
address indexed merkleDistributor,
address indexed token,
bytes32 merkleRoot,
uint32 startTime,
uint32 endTime,
address staking,
address rewardOwner,
uint256 bonusPercentage,
ITypes.FeeType feeType,
uint256 fee,
address feeCollector,
address creator
);
Fee Management Events
event FeeCollectorSet(address admin, address indexed newFeeCollector);
event SetDefaultGasFee(address admin, uint256 oldGasFee, uint256 newGasFee);
event SetDefaultTokenFee(address admin, uint256 oldTokenFee, uint256 newTokenFee);
event SetCustomFee(address admin, address indexed campaignCreator, ITypes.FeeType feeType, uint256 customFee);
Usage Patterns
1. Basic ERC20 Airdrop
// Simple token airdrop without staking
const tx = await factory.newMerkleDistributor(
tokenAddress,
merkleRoot,
startTime,
endTime,
ethers.constants.AddressZero, // No staking
ethers.constants.AddressZero, // No reward owner
0 // No bonus
);
const receipt = await tx.wait();
const distributorAddress = receipt.events[0].args.merkleDistributor;
2. Staking-Enabled Airdrop
// Airdrop with staking integration and bonus rewards
const tx = await factory.newMerkleDistributor(
tokenAddress,
merkleRoot,
startTime,
endTime,
stakingContractAddress,
rewardOwnerAddress,
1000 // 10% bonus (1000 basis points)
);
3. Native Token Airdrop
// ETH airdrop
const tx = await factory.newMerkleDistributorNative(
merkleRoot,
startTime,
endTime,
50000000000000000000 // 50 ETH
);
4. Batch Deployment
// Deploy multiple distributors
const deployments = await Promise.all([
factory.newMerkleDistributor(tokenA, rootA, start, end, staking, owner, bonus),
factory.newMerkleDistributor(tokenB, rootB, start, end, staking, owner, bonus),
factory.newMerkleDistributorNative(rootC, start, end, { value: ethAmount })
]);
Fee Structure
Default Fees
The factory maintains default fee configurations:
// Get current default fees
const defaultGasFee = await factory.defaultGasFee();
const defaultTokenFee = await factory.defaultTokenFee();
const defaultFeeType = await factory.defaultFeeType();
Custom Fees
Users can have custom fee configurations:
// Check if user has custom fees
const customFee = await factory.getCustomFee(userAddress);
if (customFee.enabled) {
console.log("Custom fee configuration:", customFee);
}
Security Considerations
Access Control
- Owner Role: Can modify fees and configurations
- Fee Collector Role: Separate from owner, can only collect fees
- Deployment: Anyone can deploy distributors (fees apply)
Validation
The factory performs validation on:
- Time Parameters: Start time < end time, end time > current time
- Addresses: Non-zero addresses where required
- Fee Ranges: Reasonable fee amounts
Best Practices
- Test Deployments: Always test on testnet first
- Fee Calculation: Account for fees when funding distributors
- Time Buffers: Allow sufficient time for claims
- Monitoring: Monitor deployments and collect fees regularly
Error Handling
Common Errors
Error | Cause | Solution |
---|---|---|
InvalidAddress() | Zero address provided | Use valid addresses |
InvalidFeeType() | Invalid fee type | Use Gas or DistributionToken |
FeeTooHigh() | Fee exceeds maximum | Reduce fee amount |
Related Contracts
- TokenOpsMerkleDistributor - ERC20 token distributor
- TokenOpsMerkleDistributorNative - Native token distributor
- ITokenOpsMerkleDistributorFactory - Factory interface
Ready to deploy your first distributor? Check out our Getting Started Guide for a step-by-step walkthrough.