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
  • 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 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
}