DisperseTokenFee Documentation
Overview
The DisperseTokenFee contract enables efficient batch distribution of ERC20 tokens to multiple recipients with a percentage-based fee structure. This contract charges fees as a percentage of the distributed tokens, making it ideal for token-based distributions where fees are paid in the same token being distributed.
🎯 Key Features:
- Percentage-Based Fees: Fees calculated as percentage of distributed tokens
- Token-Only Operations: No ETH required for distribution
- Fee Deduction Options: Pay fees on top or deduct from recipient amounts
- Per-Token Fee Tracking: Separate fee accounting for each token
- Secure Fee Management: Protected fee withdrawal and role management
Contract Details
License: BUSL-1.1
Solidity Version: 0.8.28
Fee Type: ITypes.FeeType.DistributionToken
Inheritance
IDisperseTokenFee
- Standard token fee disperse interfaceOwnable
- OpenZeppelin access control for owner functions
Dependencies
SafeERC20
- Safe token transfer operationsErrors
- Centralized error handling library
Architecture
Constants
uint256 public constant BASIS_POINTS = 10000;
ITypes.FeeType public constant FEE_TYPE = ITypes.FeeType.DistributionToken;
uint256 public immutable FEE;
uint256 public immutable DEPLOYMENT_BLOCK_NUMBER;
State Variables
address public feeCollector;
mapping(address => uint256) public tokenToFeeReserved;
Constructor
constructor(
uint256 tokenFee_,
address feeCollector_,
address creator_
)
Parameters:
tokenFee_
: Fee in basis points (max 10000)feeCollector_
: Address that will collect fees (cannot be zero)creator_
: Contract owner address
Requirements:
feeCollector_
cannot be zero addresstokenFee_
cannot exceed 10000 basis points
Fee Structure
The contract uses a percentage-based fee model:
- Fee Calculation:
(amount * FEE) / BASIS_POINTS
- Basis Points: 10000 = 100%, 250 = 2.5%, 50 = 0.5%
- Fee Currency: Same token being distributed
- Maximum Fee: 100% (10000 basis points)
Core Functions
Token Distribution
disperseToken(IERC20, address[], uint256[])
Distributes tokens with fees paid on top of recipient amounts.
function disperseToken(
IERC20 token,
address[] calldata recipients,
uint256[] calldata values
) external
Parameters:
token
: ERC20 token contract address (cannot be zero)recipients
: Array of recipient addressesvalues
: Array of token amounts to distribute
Token Approval Required: sum(values) + sum(fees)
Fee Calculation: (values[i] * FEE) / BASIS_POINTS
per recipient
Requirements:
- Token address cannot be zero
- Arrays cannot be empty
- Arrays must have equal length
- Sufficient token approval including fees
Events Emitted: TokenDispersed
disperseTokenDeductedFee(IERC20, address[], uint256[])
Distributes tokens with fees deducted from recipient amounts.
function disperseTokenDeductedFee(
IERC20 token,
address[] calldata recipients,
uint256[] calldata values
) external
Parameters:
token
: ERC20 token contract address (cannot be zero)recipients
: Array of recipient addressesvalues
: Array of gross token amounts (before fee deduction)
Token Approval Required: sum(values)
(no additional fee approval needed)
Recipients Receive: values[i] - ((values[i] * FEE) / BASIS_POINTS)
Special Case: When FEE = 0
, no fees are deducted (full amounts transferred)
Events Emitted: TokenDispersed
💡 Fee Deduction Notes:
- When
FEE = 0
, no fees are deducted (full amounts transferred) - Recipients receive the net amount after fee deduction
- Fees are still tracked and can be withdrawn by fee collector
Fee Management
Fee Collection
withdrawTokenFee(address, address, uint256)
Withdraws collected token fees.
function withdrawTokenFee(
address recipient,
address tokenAddr,
uint256 amount
) external onlyFeeCollector
Parameters:
recipient
: Address to receive withdrawn fees (cannot be zero)tokenAddr
: Token contract address (cannot be zero)amount
: Amount to withdraw (0 = withdraw all available)
Access: Fee collector only
Requirements:
- Both addresses cannot be zero
- Must have available fees for the token
- Amount cannot exceed available fees
Events Emitted: TokenFeeWithdrawn
tokenToFeeReserved(address)
Returns the amount of fees reserved for a specific token.
function tokenToFeeReserved(address token) external view returns (uint256)
Parameters:
token
: Token contract address to query
Returns: Amount of fees reserved in token units
transferFeeCollectorRole(address)
Transfers fee collector role to new address.
function transferFeeCollectorRole(address newFeeCollector)
external onlyFeeCollector
Parameters:
newFeeCollector
: New fee collector address (cannot be zero)
Access: Fee collector only
Requirements:
- New fee collector cannot be zero address
Events Emitted: FeeCollectorUpdated
Recovery Functions
withdrawERC20(address)
Withdraws non-fee tokens from the contract.
function withdrawERC20(address tokenAddr) external onlyOwner
Parameters:
tokenAddr
: Token contract address (cannot be zero)
Access: Owner only
Important: Only withdraws balance - reservedFees
, protecting collected fees
Requirements:
- Token address cannot be zero
- Must have available balance after excluding reserved fees
Events Emitted: ERC20Withdrawn
Events
Distribution Events
TokenDispersed
event TokenDispersed(
address indexed sender,
address indexed token,
ITypes.FeeType feeType,
uint256 totalAmount,
uint256 totalFee
);
Emitted when tokens are distributed to recipients.
Parameters:
sender
: Address that initiated the distributiontoken
: Token contract addressfeeType
: AlwaysITypes.FeeType.DistributionToken
totalAmount
: Total tokens distributed to recipientstotalFee
: Total fees collected in tokens
Management Events
TokenFeeWithdrawn
event TokenFeeWithdrawn(
address indexed recipient,
address indexed token,
uint256 amount
);
ERC20Withdrawn
event ERC20Withdrawn(address indexed token, uint256 amount);
FeeCollectorUpdated
event FeeCollectorUpdated(
address indexed oldFeeCollector,
address indexed newFeeCollector
);
Error Handling
Common Errors
InvalidAddress()
Description: Zero address provided Common Causes:
- Zero address provided for token or recipient
- Zero address for fee collector
InvalidArrayLength()
Description: Empty array provided Common Causes:
- Empty recipients array
- Empty values array
ArrayLengthMismatch()
Description: Array lengths don't match Common Causes:
- Recipients and values arrays have different lengths
ZeroBalance()
Description: No balance available Common Causes:
- No tokens available for withdrawal
- No fees to withdraw
OnlyFeeCollector()
Description: Only fee collector can execute Common Causes:
- Non-fee collector trying to withdraw fees
- Non-fee collector trying to transfer role
Security Considerations
🔐 Security Features:
- Fee Separation: Reserved fees tracked separately from available tokens
- Input Validation: Comprehensive checks on all parameters
- Access Control: Owner and fee collector role separation
- Safe Transfers: Uses OpenZeppelin's SafeERC20 library
- Immutable Fees: Fee percentage set at deployment
Troubleshooting
Transaction Failures
"Transaction ran out of gas"
- Cause: Batch size too large
- Solution: Reduce batch size or increase gas limit
"ERC20: transfer amount exceeds allowance"
- Cause: Insufficient token approval
- Solution: Approve sufficient tokens including fees
"SafeERC20: low-level call failed"
- Cause: Token contract issue or insufficient balance
- Solution: Verify token contract and sender balance
Fee Calculation Issues
Unexpected fee amounts
- Cause: Incorrect basis points calculation
- Solution: Use
(amount * feePercentage) / 10000
formula
Fee deduction not working as expected
- Cause: Using wrong function for fee model
- Solution: Use
disperseTokenDeductedFee
for fee deduction
Next Steps
- DisperseFactory Documentation - Factory contract
- DisperseGasFee Documentation - Gas-based fee contract
- User Guide - Complete usage guide