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
- 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:
- Supports both token and ETH distributions
- Methods:
disperseToken,disperseTokenSimple,disperseEther,disperseEtherDeductedFee
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 }
);
}
}
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');
}
}
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
}