Skip to main content

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 interface
  • Ownable - OpenZeppelin access control for owner functions

Dependencies

  • SafeERC20 - Safe token transfer operations
  • Errors - 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 address
  • tokenFee_ 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 addresses
  • values: 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 addresses
  • values: 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 distribution
  • token: Token contract address
  • feeType: Always ITypes.FeeType.DistributionToken
  • totalAmount: Total tokens distributed to recipients
  • totalFee: 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