Token Distribution Overview
The TokenOps Disperse system enables efficient bulk token transfers to multiple recipients in a single transaction. This system is perfect for payroll distributions, community rewards, affiliate payments, and any scenario requiring multiple token transfers.
What is Token Disperse?
Token Disperse is a smart contract system that allows sending tokens to multiple addresses in a single transaction, dramatically reducing gas costs and simplifying bulk distribution processes.
Key Benefits
- Gas Efficiency: Single transaction for multiple transfers
- Cost Savings: Up to 60% reduction in gas costs vs individual transfers
- Simplicity: One-click bulk distributions
- Flexibility: Multiple fee models and token types supported
- Security: Audited contracts with built-in safety mechanisms
Disperse System Architecture
Disperse Contract Types
1. Token Fee Disperse
Token-based fee model
import { DisperseTokenFee, ViemProviderAdapter } from 'tokenops-sdk';
// Perfect for: Regular distributions, established tokens
const disperse = new DisperseTokenFee(contractAddress, provider);
Features:
- Fee paid in the same token being distributed
- Basis points fee structure (configurable percentage)
- Automatic fee collection with deduction options
- Three distribution methods:
disperseToken
,disperseTokenDeductedFee
,disperseTokenSimple
- Emergency recovery mechanisms
2. Gas Fee Disperse
ETH-based fee model
import { DisperseGasFee, ViemProviderAdapter } from 'tokenops-sdk';
// Perfect for: Premium distributions, gas-optimized transfers
const disperse = new DisperseGasFee(contractAddress, provider);
Features:
- Fee paid in ETH/native tokens
- Fixed ETH fee structure
- Supports both token and ETH distributions
- Methods:
disperseToken
,disperseTokenSimple
,disperseEther
,disperseEtherDeductedFee
- Lower total transaction costs for tokens
Fee Models Explained
Token Fee Model
Pay fees using the tokens being distributed:
// Example: Distribute 1000 tokens with basis points fee calculation
const disperse = new DisperseTokenFee(contractAddress, provider);
const fee = await disperse.getFee(); // Fee in basis points (e.g., 200 = 2%)
const basisPoints = await disperse.getBasisPoints(); // 10000 basis points = 100%
const amounts = [BigInt('100000000000000000000')]; // 100 tokens
const totalAmount = amounts[0];
const protocolFee = (totalAmount * fee) / basisPoints;
console.log('Protocol fee:', protocolFee); // 2 tokens (2% of 100)
console.log('Total needed:', totalAmount + protocolFee); // 102 tokens
Gas Fee Model
Pay fees in ETH while distributing tokens:
// Example: Distribute tokens with fixed ETH fee
const disperse = new DisperseGasFee(contractAddress, provider);
const gasFee = await disperse.getFee(); // Fixed ETH fee amount
// Transaction sends tokens + ETH fee
await disperse.disperseToken(
tokenAddress,
recipients,
amounts,
{
account: senderAddress,
value: gasFee // Fixed ETH fee
}
);
Factory Deployment
Deploy disperse contracts using the factory pattern:
import { DisperseFactory, ViemProviderAdapter } from 'tokenops-sdk';
import { createPublicClient, createWalletClient, http } from 'viem';
import { mainnet } from 'viem/chains';
// Initialize provider
const publicClient = createPublicClient({
chain: mainnet,
transport: http()
});
const walletClient = createWalletClient({
chain: mainnet,
transport: http(),
account: deployerAccount
});
const provider = new ViemProviderAdapter(publicClient, walletClient);
const factory = new DisperseFactory(factoryAddress, provider);
// Deploy Token Fee Disperse
const tokenFeeResult = await factory.newDisperseTokenFee({
account: deployerAddress
});
console.log('TokenFee Disperse deployed:', {
address: tokenFeeResult.disperse,
feeType: tokenFeeResult.feeType,
tokenFee: tokenFeeResult.tokenFee,
feeCollector: tokenFeeResult.feeCollector,
creator: tokenFeeResult.creator
});
// Deploy Gas Fee Disperse
const gasFeeResult = await factory.newDisperseGasFee({
account: deployerAddress
});
console.log('GasFee Disperse deployed:', {
address: gasFeeResult.disperse,
feeType: gasFeeResult.feeType,
gasFee: gasFeeResult.gasFee,
feeCollector: gasFeeResult.feeCollector,
creator: gasFeeResult.creator
});
Basic Token Distribution
Simple ERC20 Distribution
async function distributeTokens() {
const disperse = new DisperseTokenFee(contractAddress, provider);
const tokenAddress = '0x...'; // ERC20 token contract
const recipients = [
'0x742d35Cc6632C0532c718F4D8c2b1D1f8a1B9F36',
'0x123...',
'0x456...',
'0x789...'
];
const amounts = [
BigInt('100000000000000000000'), // 100 tokens
BigInt('250000000000000000000'), // 250 tokens
BigInt('75000000000000000000'), // 75 tokens
BigInt('500000000000000000000') // 500 tokens
];
// Get fee information
const fee = await disperse.getFee();
const basisPoints = await disperse.getBasisPoints();
// Calculate total amount needed (includes fees)
const totalAmount = amounts.reduce((sum, amount) => sum + amount, BigInt(0));
const totalWithFees = amounts.reduce((sum, amount) => {
return sum + amount + (amount * fee) / basisPoints;
}, BigInt(0));
// Standard distribution (fees added to amounts)
const result = await disperse.disperseToken(
tokenAddress,
recipients,
amounts,
{
account: senderAddress
}
);
console.log('Distribution completed:', {
totalRecipients: recipients.length,
totalAmount: totalAmount,
totalWithFees: totalWithFees,
transactionHash: result.transactionHash,
status: result.status,
gasUsed: result.gasUsed
});
return result;
}
// Alternative: Deduct fees from distribution amounts
async function distributeTokensDeductedFee() {
const disperse = new DisperseTokenFee(contractAddress, provider);
const result = await disperse.disperseTokenDeductedFee(
tokenAddress,
recipients,
amounts,
{
account: senderAddress
}
);
// Recipients receive slightly less due to fee deduction
return result;
}
// Simplified version (basic distribution)
async function distributeTokensSimple() {
const disperse = new DisperseTokenFee(contractAddress, provider);
const result = await disperse.disperseTokenSimple(
tokenAddress,
recipients,
amounts,
{
account: senderAddress
}
);
return result;
}
ETH Distribution
async function distributeETH() {
const disperse = new DisperseGasFee(contractAddress, provider);
const recipients = [
'0x742d35Cc6632C0532c718F4D8c2b1D1f8a1B9F36',
'0x123...',
'0x456...'
];
const amounts = [
BigInt('1000000000000000000'), // 1 ETH
BigInt('500000000000000000'), // 0.5 ETH
BigInt('2000000000000000000') // 2 ETH
];
const totalETH = amounts.reduce((sum, amount) => sum + amount, BigInt(0));
const gasFee = await disperse.getFee(); // Fixed ETH fee
// Standard ETH distribution
const result = await disperse.disperseEther(
recipients,
amounts,
{
account: senderAddress,
value: totalETH + gasFee // Total ETH + protocol fee
}
);
return result;
}
// Alternative: Deduct fee from distribution amounts
async function distributeETHDeductedFee() {
const disperse = new DisperseGasFee(contractAddress, provider);
const recipients = [
'0x742d35Cc6632C0532c718F4D8c2b1D1f8a1B9F36',
'0x123...',
'0x456...'
];
const amounts = [
BigInt('1000000000000000000'), // 1 ETH
BigInt('500000000000000000'), // 0.5 ETH
BigInt('2000000000000000000') // 2 ETH
];
const totalETH = amounts.reduce((sum, amount) => sum + amount, BigInt(0));
const result = await disperse.disperseEtherDeductedFee(
recipients,
amounts,
{
account: senderAddress,
value: totalETH // Fee deducted from amounts
}
);
return result;
}
Advanced Distribution Patterns
1. Payroll Distribution
Automate employee payments:
interface Employee {
address: string;
salary: bigint;
bonus: bigint;
role: string;
startDate: Date;
}
class PayrollManager {
private disperse: DisperseTokenFee;
private payrollToken: string;
constructor(disperseAddress: string, tokenAddress: string) {
this.disperse = new DisperseTokenFee(disperseAddress, provider);
this.payrollToken = tokenAddress;
}
async processMonthlyPayroll(employees: Employee[]) {
const currentDate = new Date();
const eligibleEmployees = employees.filter(emp =>
emp.startDate <= currentDate
);
const recipients: string[] = [];
const amounts: bigint[] = [];
eligibleEmployees.forEach(employee => {
recipients.push(employee.address);
// Calculate total compensation
const totalPay = employee.salary + employee.bonus;
amounts.push(totalPay);
});
const result = await this.disperse.disperseToken(
this.payrollToken,
recipients,
amounts,
{ account: payrollAdminAddress }
);
// Log payroll event
console.log('Payroll processed:', {
month: currentDate.toISOString().slice(0, 7),
employeeCount: eligibleEmployees.length,
totalPaid: amounts.reduce((sum, amount) => sum + amount, BigInt(0)),
transactionHash: result.hash
});
return result;
}
async processBonusDistribution(bonuses: Map<string, bigint>) {
const recipients = Array.from(bonuses.keys());
const amounts = Array.from(bonuses.values());
return await this.disperse.disperseToken(
this.payrollToken,
recipients,
amounts,
{ account: payrollAdminAddress }
);
}
}
2. Affiliate Rewards
Distribute commission payments:
interface AffiliateEarnings {
affiliateAddress: string;
salesVolume: bigint;
commissionRate: number; // Percentage
referralBonus: bigint;
tier: 'bronze' | 'silver' | 'gold' | 'platinum';
}
class AffiliateRewardSystem {
private disperse: DisperseGasFee;
private rewardToken: string;
constructor(disperseAddress: string, tokenAddress: string) {
this.disperse = new DisperseGasFee(disperseAddress, provider);
this.rewardToken = tokenAddress;
}
async distributeCommissions(earnings: AffiliateEarnings[]) {
const tierMultipliers = {
bronze: 1.0,
silver: 1.1,
gold: 1.25,
platinum: 1.5
};
const recipients: string[] = [];
const amounts: bigint[] = [];
earnings.forEach(earning => {
const baseCommission = (earning.salesVolume * BigInt(earning.commissionRate)) / BigInt(100);
const tierMultiplier = tierMultipliers[earning.tier];
const adjustedCommission = BigInt(Math.floor(Number(baseCommission) * tierMultiplier));
const totalReward = adjustedCommission + earning.referralBonus;
recipients.push(earning.affiliateAddress);
amounts.push(totalReward);
});
// Calculate ETH fee for gas-fee model
const estimatedGasFee = BigInt('2000000000000000'); // 0.002 ETH
const result = await this.disperse.disperseToken(
this.rewardToken,
recipients,
amounts,
{
account: rewardAdminAddress,
value: estimatedGasFee
}
);
return result;
}
}
3. Community Rewards
Distribute tokens to community members:
interface CommunityContribution {
userAddress: string;
contributionType: 'content' | 'development' | 'moderation' | 'bug_report';
score: number;
multiplier: number;
}
async function distributeCommunityRewards(contributions: CommunityContribution[]) {
const baseRewardPerPoint = BigInt('1000000000000000000'); // 1 token per point
const recipients: string[] = [];
const amounts: bigint[] = [];
contributions.forEach(contribution => {
const reward = baseRewardPerPoint *
BigInt(contribution.score) *
BigInt(Math.floor(contribution.multiplier * 100)) /
BigInt(100);
recipients.push(contribution.userAddress);
amounts.push(reward);
});
const disperse = new DisperseTokenFee(disperseAddress, provider);
const result = await disperse.disperseToken(
communityTokenAddress,
recipients,
amounts,
{ account: communityAdminAddress }
);
return result;
}
Fee Calculation & Management
Dynamic Fee Calculation
interface FeeConfig {
tokenFeePercentage: number; // For token fee model
gasFeeFixed: bigint; // Fixed ETH fee
gasFeePerRecipient: bigint; // ETH fee per recipient
volumeDiscounts: Array<{
minAmount: bigint;
discountPercentage: number;
}>;
}
class FeeCalculator {
private config: FeeConfig;
constructor(config: FeeConfig) {
this.config = config;
}
calculateTokenFee(totalAmount: bigint, recipientCount: number): bigint {
const baseFee = (totalAmount * BigInt(this.config.tokenFeePercentage)) / BigInt(100);
// Apply volume discounts
let discount = 0;
for (const tier of this.config.volumeDiscounts) {
if (totalAmount >= tier.minAmount) {
discount = tier.discountPercentage;
}
}
const discountAmount = (baseFee * BigInt(discount)) / BigInt(100);
return baseFee - discountAmount;
}
calculateGasFee(recipientCount: number): bigint {
return this.config.gasFeeFixed +
(this.config.gasFeePerRecipient * BigInt(recipientCount));
}
estimateTotalCost(
model: 'token' | 'gas',
totalAmount: bigint,
recipientCount: number
): { fee: bigint; total: bigint } {
if (model === 'token') {
const fee = this.calculateTokenFee(totalAmount, recipientCount);
return {
fee,
total: totalAmount + fee
};
} else {
const fee = this.calculateGasFee(recipientCount);
return {
fee,
total: totalAmount // Total tokens (ETH fee separate)
};
}
}
}
// Usage example
const feeConfig: FeeConfig = {
tokenFeePercentage: 2, // 2%
gasFeeFixed: BigInt('1000000000000000'), // 0.001 ETH
gasFeePerRecipient: BigInt('100000000000000'), // 0.0001 ETH per recipient
volumeDiscounts: [
{ minAmount: BigInt('10000000000000000000000'), discountPercentage: 10 }, // 10% off for 10k+ tokens
{ minAmount: BigInt('100000000000000000000000'), discountPercentage: 25 } // 25% off for 100k+ tokens
]
};
const calculator = new FeeCalculator(feeConfig);
const costs = calculator.estimateTotalCost('token', BigInt('50000000000000000000000'), 100);
console.log('Token fee model costs:', costs);
Security Features
Input Validation
function validateDistributionInputs(
recipients: string[],
amounts: bigint[]
): void {
// Check array lengths match
if (recipients.length !== amounts.length) {
throw new Error('Recipients and amounts arrays must have the same length');
}
// Check minimum requirements
if (recipients.length === 0) {
throw new Error('Must have at least one recipient');
}
if (recipients.length > 1000) {
throw new Error('Too many recipients (max 1000 per transaction)');
}
// Validate addresses
recipients.forEach((address, index) => {
if (!isValidAddress(address)) {
throw new Error(`Invalid address at index ${index}: ${address}`);
}
});
// Validate amounts
amounts.forEach((amount, index) => {
if (amount <= 0) {
throw new Error(`Invalid amount at index ${index}: ${amount}`);
}
});
// Check for duplicates
const uniqueRecipients = new Set(recipients.map(addr => addr.toLowerCase()));
if (uniqueRecipients.size !== recipients.length) {
throw new Error('Duplicate recipients detected');
}
}
Fee Withdrawal and Recovery
class FeeManagement {
private disperse: DisperseTokenFee;
private adminAddress: string;
constructor(disperseAddress: string, adminAddress: string) {
this.disperse = new DisperseTokenFee(disperseAddress, provider);
this.adminAddress = adminAddress;
}
async withdrawTokenFees(
recipient: string,
tokenAddress: string,
amount: bigint
): Promise<any> {
// Withdraw collected token fees
const result = await this.disperse.withdrawTokenFee(
recipient,
tokenAddress,
amount,
{ account: this.adminAddress }
);
console.log('Token fee withdrawal completed:', {
recipient: result.recipient,
token: result.token,
amount: result.amount,
transactionHash: result.transactionHash
});
return result;
}
async emergencyWithdrawERC20(tokenAddress: string): Promise<any> {
// Emergency withdrawal of any ERC20 tokens
const result = await this.disperse.withdrawERC20(
tokenAddress,
{ account: this.adminAddress }
);
console.log('Emergency ERC20 withdrawal completed:', {
token: result.token,
amount: result.amount,
transactionHash: result.transactionHash
});
return result;
}
async checkFeeReserved(tokenAddress: string): Promise<bigint> {
const reserved = await this.disperse.getTokenToFeeReserved(tokenAddress);
console.log(`Fee reserved for ${tokenAddress}:`, reserved);
return reserved;
}
}
// For Gas Fee Disperse
class GasFeeManagement {
private disperse: DisperseGasFee;
private adminAddress: string;
constructor(disperseAddress: string, adminAddress: string) {
this.disperse = new DisperseGasFee(disperseAddress, provider);
this.adminAddress = adminAddress;
}
async withdrawGasFees(
recipient: string,
amount: bigint
): Promise<any> {
// Withdraw collected ETH fees
const result = await this.disperse.withdrawGasFee(
recipient,
amount,
{ account: this.adminAddress }
);
console.log('Gas fee withdrawal completed:', {
recipient: result.recipient,
withdrawAmount: result.withdrawAmount,
transactionHash: result.transactionHash
});
return result;
}
async emergencyWithdrawERC20(tokenAddress: string): Promise<any> {
// Emergency withdrawal of any ERC20 tokens
const result = await this.disperse.withdrawERC20(
tokenAddress,
{ account: this.adminAddress }
);
return result;
}
}
Event Monitoring
Monitor disperse contract events:
interface ERC20WithdrawnEvent {
token: string;
amount: bigint;
}
interface TokenFeeWithdrawnEvent {
recipient: string;
token: string;
amount: bigint;
}
interface GasFeeWithdrawnEvent {
recipient: string;
withdrawAmount: bigint;
}
interface FeeCollectorUpdatedEvent {
oldFeeCollector: string;
newFeeCollector: string;
}
class DisperseMonitor {
private disperse: DisperseTokenFee;
private eventHandlers: Map<string, Function[]> = new Map();
constructor(disperseAddress: string) {
this.disperse = new DisperseTokenFee(disperseAddress, provider);
this.setupEventListeners();
}
private setupEventListeners() {
// Monitor ERC20 withdrawals
this.disperse.on('ERC20Withdrawn', (event) => {
const withdrawalEvent: ERC20WithdrawnEvent = {
token: event.token,
amount: event.amount
};
this.handleEvent('erc20Withdrawal', withdrawalEvent);
});
// Monitor token fee withdrawals
this.disperse.on('TokenFeeWithdrawn', (event) => {
const feeWithdrawalEvent: TokenFeeWithdrawnEvent = {
recipient: event.recipient,
token: event.token,
amount: event.amount
};
this.handleEvent('tokenFeeWithdrawal', feeWithdrawalEvent);
});
// Monitor fee collector updates
this.disperse.on('FeeCollectorUpdated', (event) => {
const feeCollectorEvent: FeeCollectorUpdatedEvent = {
oldFeeCollector: event.oldFeeCollector,
newFeeCollector: event.newFeeCollector
};
this.handleEvent('feeCollectorUpdate', feeCollectorEvent);
});
}
private handleEvent(eventType: string, data: any) {
const handlers = this.eventHandlers.get(eventType) || [];
handlers.forEach(handler => {
try {
handler(data);
} catch (error) {
console.error(`Error in ${eventType} handler:`, error);
}
});
}
onERC20Withdrawal(handler: (event: ERC20WithdrawnEvent) => void) {
const handlers = this.eventHandlers.get('erc20Withdrawal') || [];
handlers.push(handler);
this.eventHandlers.set('erc20Withdrawal', handlers);
}
onTokenFeeWithdrawal(handler: (event: TokenFeeWithdrawnEvent) => void) {
const handlers = this.eventHandlers.get('tokenFeeWithdrawal') || [];
handlers.push(handler);
this.eventHandlers.set('tokenFeeWithdrawal', handlers);
}
onFeeCollectorUpdate(handler: (event: FeeCollectorUpdatedEvent) => void) {
const handlers = this.eventHandlers.get('feeCollectorUpdate') || [];
handlers.push(handler);
this.eventHandlers.set('feeCollectorUpdate', handlers);
}
}
// Gas Fee Disperse Monitor
class GasFeeDisperseMonitor {
private disperse: DisperseGasFee;
private eventHandlers: Map<string, Function[]> = new Map();
constructor(disperseAddress: string) {
this.disperse = new DisperseGasFee(disperseAddress, provider);
this.setupEventListeners();
}
private setupEventListeners() {
// Monitor gas fee withdrawals
this.disperse.on('GasFeeWithdrawn', (event) => {
const gasFeeEvent: GasFeeWithdrawnEvent = {
recipient: event.recipient,
withdrawAmount: event.withdrawAmount
};
this.handleEvent('gasFeeWithdrawal', gasFeeEvent);
});
}
private handleEvent(eventType: string, data: any) {
const handlers = this.eventHandlers.get(eventType) || [];
handlers.forEach(handler => {
try {
handler(data);
} catch (error) {
console.error(`Error in ${eventType} handler:`, error);
}
});
}
onGasFeeWithdrawal(handler: (event: GasFeeWithdrawnEvent) => void) {
const handlers = this.eventHandlers.get('gasFeeWithdrawal') || [];
handlers.push(handler);
this.eventHandlers.set('gasFeeWithdrawal', handlers);
}
}
// Usage
const monitor = new DisperseMonitor(disperseAddress);
monitor.onTokenFeeWithdrawal((event) => {
console.log('Token fee withdrawal:', {
recipient: event.recipient,
token: event.token,
amount: event.amount
});
// Send notification
sendSlackNotification(`Token fee withdrawn: ${event.amount} tokens to ${event.recipient}`);
});
monitor.onERC20Withdrawal((event) => {
console.log('Emergency ERC20 withdrawal:', {
token: event.token,
amount: event.amount
});
});
Performance Optimization
Gas Estimation
async function estimateDistributionGas(
disperseAddress: string,
tokenAddress: string,
recipients: string[],
amounts: bigint[]
): Promise<{
estimatedGas: bigint;
estimatedCost: bigint;
recommendation: string;
}> {
const disperse = new DisperseTokenFee(disperseAddress, provider);
try {
// Use viem provider to estimate gas
const gasEstimate = await provider.viemClient.estimateContractGas({
address: disperseAddress,
abi: DisperseTokenFeeAbi,
functionName: 'disperseToken',
args: [tokenAddress, recipients, amounts],
account: senderAddress
});
// Get current gas price
const gasPrice = await provider.viemClient.getGasPrice();
const estimatedCost = gasEstimate * gasPrice;
// Provide recommendations
let recommendation = '';
if (recipients.length > 200) {
recommendation = 'Consider batching into smaller groups for better gas efficiency';
} else if (recipients.length < 10) {
recommendation = 'Small batch - consider accumulating more recipients';
} else {
recommendation = 'Optimal batch size for gas efficiency';
}
return {
estimatedGas: gasEstimate,
estimatedCost,
recommendation
};
} catch (error) {
throw new Error(`Gas estimation failed: ${error.message}`);
}
}
Integration Examples
CSV Import and Distribution
import * as fs from 'fs';
import * as csv from 'csv-parser';
interface CSVRecord {
address: string;
amount: string;
name?: string;
email?: string;
}
async function processCSVDistribution(csvFilePath: string): Promise<any> {
const records: CSVRecord[] = [];
return new Promise((resolve, reject) => {
fs.createReadStream(csvFilePath)
.pipe(csv())
.on('data', (data: CSVRecord) => {
// Validate and clean data
if (isValidAddress(data.address) && parseFloat(data.amount) > 0) {
records.push(data);
} else {
console.warn(`Invalid record skipped: ${JSON.stringify(data)}`);
}
})
.on('end', async () => {
try {
const recipients = records.map(r => r.address);
const amounts = records.map(r => BigInt(parseFloat(r.amount) * 1e18)); // Convert to wei
const result = await disperse.disperseToken(
tokenAddress,
recipients,
amounts,
{ account: senderAddress }
);
resolve({
recordsProcessed: records.length,
totalAmount: amounts.reduce((sum, amount) => sum + amount, BigInt(0)),
transactionHash: result.hash
});
} catch (error) {
reject(error);
}
})
.on('error', reject);
});
}
Web3 Frontend Integration
// Frontend helper for disperse operations
class DisperseFrontend {
private disperse: DisperseTokenFee;
private provider: any;
constructor(disperseAddress: string, web3Provider: any) {
this.provider = web3Provider;
this.disperse = new DisperseTokenFee(disperseAddress, web3Provider);
}
async connectWallet(): Promise<string> {
const accounts = await this.provider.request({
method: 'eth_requestAccounts'
});
return accounts[0];
}
async checkTokenBalance(tokenAddress: string, userAddress: string): Promise<bigint> {
const token = new ethers.Contract(tokenAddress, ERC20_ABI, this.provider);
return await token.balanceOf(userAddress);
}
async approveTokens(tokenAddress: string, amount: bigint): Promise<any> {
const token = new ethers.Contract(tokenAddress, ERC20_ABI, this.provider.getSigner());
return await token.approve(this.disperse.address, amount);
}
async distributeWithProgress(
tokenAddress: string,
recipients: string[],
amounts: bigint[],
onProgress: (progress: number) => void
): Promise<any> {
// Split into batches for progress tracking
const batchSize = 50;
const batches = [];
for (let i = 0; i < recipients.length; i += batchSize) {
batches.push({
recipients: recipients.slice(i, i + batchSize),
amounts: amounts.slice(i, i + batchSize)
});
}
const results = [];
for (let i = 0; i < batches.length; i++) {
const batch = batches[i];
const result = await this.disperse.disperseToken(
tokenAddress,
batch.recipients,
batch.amounts,
{ account: await this.connectWallet() }
);
results.push(result);
// Update progress
const progress = ((i + 1) / batches.length) * 100;
onProgress(progress);
}
return results;
}
}
Complete API Reference
DisperseTokenFee
Read Methods
getFee(): Promise<bigint>
- Get token fee in basis pointsgetFeeType(): Promise<bigint>
- Get fee type enum (0 = token fee)getOwner(): Promise<Address>
- Get contract ownergetFeeCollector(): Promise<Address>
- Get fee collector addressgetTokenToFeeReserved(token: Address): Promise<bigint>
- Get reserved fee amount for tokengetBasisPoints(): Promise<bigint>
- Get basis points constant (10000)getDeploymentBlockNumber(): Promise<bigint>
- Get contract deployment block
Write Methods
disperseToken(token: Address, recipients: Address[], values: bigint[], options: { account: Address }): Promise<TransactionReceipt>
disperseTokenDeductedFee(token: Address, recipients: Address[], values: bigint[], options: { account: Address }): Promise<TransactionReceipt>
disperseTokenSimple(token: Address, recipients: Address[], values: bigint[], options: { account: Address }): Promise<TransactionReceipt>
withdrawTokenFee(recipient: Address, tokenAddress: Address, amount: bigint, options: { account: Address }): Promise<TokenFeeWithdrawn>
withdrawERC20(token: Address, options: { account: Address }): Promise<ERC20Withdrawn>
DisperseGasFee
Read Methods
getFee(): Promise<bigint>
- Get fixed gas fee amount in ETHgetFeeType(): Promise<bigint>
- Get fee type enum (1 = gas fee)getOwner(): Promise<Address>
- Get contract ownergetFeeCollector(): Promise<Address>
- Get fee collector addressgetDeploymentBlockNumber(): Promise<bigint>
- Get contract deployment block
Write Methods
disperseToken(token: Address, recipients: Address[], values: bigint[], options: { account: Address; value: bigint }): Promise<TransactionReceipt>
disperseTokenSimple(token: Address, recipients: Address[], values: bigint[], options: { account: Address; value: bigint }): Promise<TransactionReceipt>
disperseEther(recipients: Address[], values: bigint[], options: { account: Address; value: bigint }): Promise<TransactionReceipt>
disperseEtherDeductedFee(recipients: Address[], values: bigint[], options: { account: Address; value: bigint }): Promise<TransactionReceipt>
withdrawGasFee(recipient: Address, amount: bigint, options: { account: Address }): Promise<GasFeeWithdrawn>
withdrawERC20(token: Address, options: { account: Address }): Promise<ERC20Withdrawn>
DisperseFactory
Read Methods
getFeeCollector(): Promise<Address>
- Get factory fee collectorgetOwner(): Promise<Address>
- Get factory ownerdefaultGasFee(): Promise<bigint>
- Get default gas feedefaultTokenFee(): Promise<bigint>
- Get default token feedefaultFeeType(): Promise<bigint>
- Get default fee type
Write Methods
newDisperseTokenFee(options: { account: Address }): Promise<DisperseTokenFeeCreated>
newDisperseGasFee(options: { account: Address }): Promise<DisperseGasFeeCreated>
Type Definitions
import { Address } from 'viem';
// Factory return types
export type DisperseTokenFeeCreated = {
disperse: Address;
feeType: bigint;
tokenFee: bigint;
feeCollector: Address;
creator: Address;
};
export type DisperseGasFeeCreated = {
disperse: Address;
feeType: bigint;
gasFee: bigint;
feeCollector: Address;
creator: Address;
};
// Withdrawal event types
export type ERC20Withdrawn = {
token: Address;
amount: bigint;
};
export type TokenFeeWithdrawn = {
recipient: Address;
token: Address;
amount: bigint;
};
export type GasFeeWithdrawn = {
recipient: Address;
withdrawAmount: bigint;
};
// Fee types enum
export enum FeeType {
TOKEN_FEE = 0,
GAS_FEE = 1
}
Next Steps
Now that you understand the Disperse system:
- Token Fee Model - Detailed token-based fee implementation covered in this documentation
- Gas Fee Model - ETH-based fee optimization detailed above
- Integration Examples - Real-world implementation patterns demonstrated throughout
- Security Best Practices - Advanced security considerations covered in this guide