How Rotating Pools Work
Technical implementation of blockchain ROSCAs on KhipuVault - smart contracts, rotation schedules, and payout mechanics.
How Rotating Pools Work
KhipuVault brings traditional ROSCAs to blockchain with smart contract enforcement, automated distributions, and transparent record-keeping.
Architecture Overview
┌─────────────────┐
│ Pool Creator │ Creates pool with parameters
└────────┬────────┘
│
↓
┌─────────────────────────────────────────┐
│ RotatingPool Smart Contract │
│ - Members list │
│ - Contribution amount & schedule │
│ - Rotation order │
│ - Collateral requirements │
│ - Penalty rules │
└────────┬────────────────────────────────┘
│
↓
┌─────────────────────────────────────────┐
│ Members Join │
│ - Approve MUSD spending │
│ - Lock collateral (optional) │
│ - Accept pool rules │
└────────┬────────────────────────────────┘
│
↓
┌─────────────────────────────────────────┐
│ Rotation Cycle Begins │
│ - Automated contributions │
│ - Weekly/biweekly/monthly schedule │
│ - Each round → 1 member receives pot │
└────────┬────────────────────────────────┘
│
↓
┌─────────────────────────────────────────┐
│ Payouts Execute │
│ - Smart contract transfers full pot │
│ - Collateral returned to recipient │
│ - Continue to next member │
└─────────────────────────────────────────┘Pool Creation Parameters
When creating a rotating pool, the organizer sets:
Basic Configuration
1. Contribution Amount
contributionAmount: 100 MUSDHow much each member pays per round.
2. Rotation Schedule
rotationInterval: 7 days // Weekly
// OR
rotationInterval: 14 days // Biweekly
// OR
rotationInterval: 30 days // Monthly3. Member Count
maxMembers: 10Total participants (typically 5-20).
4. Start Date
startDate: 1735689600 // Unix timestampWhen first contributions are due.
Advanced Options
5. Collateral Requirement
collateralPercentage: 50 // 50% of total potOptional security deposit to prevent defaults.
6. Late Payment Grace Period
gracePeriod: 2 daysExtra time before penalties apply.
7. Penalty Rate
penaltyRate: 500 // 5% per late periodFee for missed deadlines.
8. Emergency Withdrawal
allowEmergencyExit: true
earlyExitPenalty: 1000 // 10% penaltyRotation Order
Fixed-Order (Default)
How it works: Order determined at pool creation.
Selection methods:
- First-come, first-served - Join order
- Lottery - Random assignment on-chain
- Manual - Organizer sets order
- Consensus - Members vote
rotationOrder: [
0xAddress1, // Receives Round 1
0xAddress2, // Receives Round 2
0xAddress3, // Receives Round 3
...
]Advantages:
- Predictable
- Transparent
- Fair
- Simple
Example:
10 members, $100/month each
Round 1 (Jan): All pay $100 → Member A gets $1,000
Round 2 (Feb): All pay $100 → Member B gets $1,000
Round 3 (Mar): All pay $100 → Member C gets $1,000
...
Round 10 (Oct): All pay $100 → Member J gets $1,000
CYCLE COMPLETEBidding-Order (Advanced)
How it works: Members bid each round for early payout.
function bidForEarlyPayout(uint256 discountBps) external {
// Offer to receive less for earlier payout
// Example: Bid 500 bps (5%) discount
// Receive $950 instead of $1,000
}Advantages:
- Rewards urgency
- Creates price discovery
- Flexible priority
Disadvantages:
- More complex
- Favors wealthy bidders
- Requires active participation
Contribution Mechanics
Automatic Pull Payments
Unlike manual collection, KhipuVault automates contributions:
// Step 1: One-time approval
musd.approve(rotatingPoolAddress, type(uint256).max)
// Step 2: Smart contract pulls on schedule
function collectRoundContributions() external {
for (uint i = 0; i < members.length; i++) {
musd.transferFrom(members[i], address(this), contributionAmount);
}
// Immediately distribute to current recipient
distributeRound();
}Advantages:
- No manual transfers
- Atomic (all-or-nothing)
- Time-locked to schedule
- Transparent execution
Contribution States
Each member's payment status:
enum ContributionStatus {
PENDING, // Not yet due
PAID, // Paid on time
LATE, // In grace period
DEFAULTED, // Failed to pay
EXCUSED // Emergency exception
}Payout Distribution
Distribution Process
function distributeRound() internal {
address recipient = rotationOrder[currentRound];
// Calculate total pot
uint256 pot = contributionAmount * members.length;
// Add yields earned (if any)
uint256 yields = calculateAccumulatedYields();
// Deduct platform fee
uint256 fee = (pot + yields) * platformFeeBps / 10000;
// Transfer to recipient
uint256 payout = pot + yields - fee;
musd.transfer(recipient, payout);
// Return recipient's collateral
if (collateralPercentage > 0) {
musd.transfer(recipient, collateral[recipient]);
}
// Emit event
emit RoundDistributed(currentRound, recipient, payout);
// Move to next round
currentRound++;
}Enhanced Yields
Bonus feature: Contributions earn yields while waiting for distribution.
If the pool holds funds for a few days before payout, those funds earn yields on Mezo. This increases the pot size slightly.
Example:
10 members × $100 = $1,000 base pot
+ $15 yields earned over 7 days (at 12% APY)
- $10 platform fee (10% of yields)
= $1,005 final payoutDefault Prevention
Collateral System
Purpose: Ensure members can't default after receiving payout.
Mechanism:
// Member A joins and locks 50% collateral
function joinPool() external {
// Pay collateral upfront
uint256 collateralAmount = (contributionAmount * maxMembers * collateralPercentage) / 100;
musd.transferFrom(msg.sender, address(this), collateralAmount);
// Track collateral
collateral[msg.sender] = collateralAmount;
// Add to members
members.push(msg.sender);
}
// Member A receives payout → collateral returned
// If Member A later defaults → collateral slashedExample:
Pool: 10 members, $100/round
Total risk: 10 × $100 = $1,000
Collateral required: 50%
Member A deposits: $500 collateral
Member A receives payout in Round 1: $1,000 + $500 = $1,500 total back
Member A must still contribute: 9 more rounds × $100 = $900
If defaults: Loses $500 collateral (covers 5 rounds)Multi-Signature Requirements
For high-value pools:
// Require multiple organizers
function setMultiSigOrganizers(address[] memory organizers, uint256 threshold) external {
// Example: 3 of 5 organizers must approve
multiSigThreshold = threshold;
multiSigOrganizers = organizers;
}
// Major actions need approval
function executeEmergencyAction() external {
require(multiSigApprovals >= multiSigThreshold, "Insufficient approvals");
// Execute action
}Penalty Escalation
Late payment penalties:
function calculatePenalty(address member) public view returns (uint256) {
uint256 daysLate = (block.timestamp - dueDate[member]) / 1 day;
if (daysLate == 0) return 0;
if (daysLate <= gracePeriod) return 0;
// Escalating penalty: 5% first week, 10% second week, etc.
uint256 weeksLate = (daysLate - gracePeriod) / 7;
uint256 penalty = contributionAmount * penaltyRate * weeksLate / 10000;
return penalty;
}Default consequences:
- First late payment: 5% penalty + warning
- Second late payment: 10% penalty + collateral at risk
- Third late payment: Expelled, collateral slashed, banned
Pool States
Lifecycle
enum PoolState {
FORMING, // Accepting members
ACTIVE, // Running cycles
COMPLETED, // All rounds finished
EMERGENCY, // Paused for issue resolution
DISSOLVED // Terminated early
}State Transitions
FORMING (0-10 members)
↓ (when full or organizer starts)
ACTIVE (running rounds)
↓ (all members received)
COMPLETED (success)
OR
ACTIVE
↓ (critical default or consensus)
EMERGENCY
↓ (resolved or voted to end)
DISSOLVED (refund logic)Security Features
Smart Contract Audits
All rotating pool contracts are audited by Mezo security team.
Audits verify:
- No fund loss bugs
- Fair distribution logic
- Proper access controls
- Reentrancy protection
On-Chain Verification
Every action is verifiable:
// Check contribution history
function getContributionHistory(address member) external view returns (Contribution[] memory);
// Verify rotation order
function getRotationOrder() external view returns (address[] memory);
// Audit payout records
function getPayoutHistory() external view returns (Payout[] memory);Emergency Controls
Member-initiated:
// Request emergency exit (with penalty)
function requestEmergencyExit() external {
// Calculate penalty
uint256 penalty = calculateExitPenalty(msg.sender);
// Refund remaining balance minus penalty
uint256 refund = getMemberEquity(msg.sender) - penalty;
// Transfer
musd.transfer(msg.sender, refund);
// Remove from pool
removeMember(msg.sender);
}Organizer-initiated:
// Pause in emergency
function pausePool() external onlyOrganizer {
require(state == PoolState.ACTIVE, "Not active");
state = PoolState.EMERGENCY;
}
// Vote to dissolve
function voteToDisssolve() external onlyMember {
dissolveVotes[msg.sender] = true;
if (countDissolveVotes() > members.length / 2) {
dissolvePool();
}
}Transparency Dashboard
Members can monitor in real-time:
Pool Health Metrics:
- On-time payment rate
- Current round status
- Next payout date and recipient
- Total volume processed
- Average yields earned
Member-Specific:
- Your contribution history
- Your upcoming payment due date
- Your projected payout date
- Your penalty status
- Your collateral balance
Learn More
Now that you understand the technical implementation:
- Creating a Pool - Setup walkthrough
- Participating - Join and contribute
- Receiving Payout - Claim your turn
- FAQ - Common questions
Ready to experience blockchain ROSCAs? Start participating →