Skip to main content

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 points
  • getFeeType(): Promise<bigint> - Get fee type enum (0 = token fee)
  • getOwner(): Promise<Address> - Get contract owner
  • getFeeCollector(): Promise<Address> - Get fee collector address
  • getTokenToFeeReserved(token: Address): Promise<bigint> - Get reserved fee amount for token
  • getBasisPoints(): 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 ETH
  • getFeeType(): Promise<bigint> - Get fee type enum (1 = gas fee)
  • getOwner(): Promise<Address> - Get contract owner
  • getFeeCollector(): Promise<Address> - Get fee collector address
  • getDeploymentBlockNumber(): 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 collector
  • getOwner(): Promise<Address> - Get factory owner
  • defaultGasFee(): Promise<bigint> - Get default gas fee
  • defaultTokenFee(): Promise<bigint> - Get default token fee
  • defaultFeeType(): 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:

  1. Token Fee Model - Detailed token-based fee implementation covered in this documentation
  2. Gas Fee Model - ETH-based fee optimization detailed above
  3. Integration Examples - Real-world implementation patterns demonstrated throughout
  4. Security Best Practices - Advanced security considerations covered in this guide