Pool Manager - Detailed Design Document
Architecture Version: 3.0 | Last Updated: January 2026 Pattern References: Network Pool Architecture, Multi-Agent Coordination, Financial Transaction Systems
1. Executive Overview
The Pool Manager is the financial backbone of the Fantasy Service, responsible for managing prize pools, entry fees, platform commissions, and fund distributions. It implements a network pool architecture that enables sophisticated fund management across multiple contests, with support for guaranteed pools, dynamic prize structures, and multi-agent coordination.
Think of the Pool Manager like a sophisticated escrow system: It holds all entry fees in trust, calculates prize distributions based on contest outcomes, and ensures every rupee is accounted for with full audit trails.
Key Capabilities:
- Atomic fund operations with ACID guarantees
- Network pool aggregation across related contests
- Dynamic prize calculation based on participation
- Multi-agent coordination for complex distributions
- Real-time balance tracking with sub-second updates
- Complete audit trail for regulatory compliance
2. Core Architecture
2.1 High-Level System Design
What this diagram shows: The Pool Manager receives requests from Contest Engine and Auction Engine. The Pool Controller manages pool lifecycle, the Fund Calculator computes distributions, and the Distribution Engine executes payouts. All financial operations go through the Transaction Manager with Saga pattern for distributed consistency.
3. Pool Types & Structures
3.1 Pool Type Hierarchy
3.2 Pool Configuration
interface Pool {
id: string;
type: PoolType;
status: PoolStatus;
contestId: string;
config: PoolConfig;
financials: PoolFinancials;
distribution: DistributionConfig;
createdAt: Date;
updatedAt: Date;
}
type PoolType =
| 'STANDARD'
| 'GUARANTEED'
| 'NETWORK'
| 'PROGRESSIVE'
| 'PRIVATE';
type PoolStatus =
| 'CREATED'
| 'COLLECTING'
| 'LOCKED'
| 'DISTRIBUTING'
| 'SETTLED'
| 'CANCELLED';
interface PoolConfig {
entryFee: number;
maxEntries: number;
minEntries: number;
platformFeePct: number;
guaranteedPool?: number;
networkPoolId?: string;
rolloverPoolId?: string;
}
interface PoolFinancials {
totalCollected: number;
platformFee: number;
prizePool: number;
guaranteeShortfall: number;
distributed: number;
pending: number;
}
interface DistributionConfig {
type: 'FIXED' | 'PERCENTAGE' | 'TIERED';
payouts: PayoutTier[];
tiebreaker: TiebreakerRule;
}
4. Pool Lifecycle State Machine
5. Network Pool Architecture
5.1 Network Pool Concept
Network Pools aggregate funds across multiple related contests, enabling larger prize pools and cross-contest competitions.
5.2 Network Pool Implementation
interface NetworkPool {
id: string;
name: string;
status: NetworkPoolStatus;
childPools: string[];
config: NetworkPoolConfig;
aggregatedFinancials: AggregatedFinancials;
grandPrizeStructure: GrandPrizeStructure;
}
interface NetworkPoolConfig {
contributionPct: number; // % of each child pool going to network
grandPrizeAllocation: number; // % of network pool for grand prize
qualificationCriteria: QualificationCriteria;
settlementOrder: 'PARALLEL' | 'SEQUENTIAL';
}
class NetworkPoolManager {
async aggregateFunds(networkPoolId: string): Promise<AggregatedFinancials> {
const networkPool = await this.getNetworkPool(networkPoolId);
const childPools = await this.getChildPools(networkPool.childPools);
let totalContribution = 0;
const contributions: PoolContribution[] = [];
for (const childPool of childPools) {
const contribution = childPool.financials.prizePool *
networkPool.config.contributionPct;
totalContribution += contribution;
contributions.push({
poolId: childPool.id,
amount: contribution,
timestamp: new Date(),
});
}
return {
totalContribution,
grandPrizePool: totalContribution * networkPool.config.grandPrizeAllocation,
contestPrizePool: totalContribution * (1 - networkPool.config.grandPrizeAllocation),
contributions,
};
}
}
6. Fund Flow & Transactions
6.1 Entry Fee Collection Flow
6.2 Transaction Manager Implementation
class TransactionManager {
private db: Database;
private kafka: KafkaProducer;
async executeTransaction(
transaction: PoolTransaction
): Promise<TransactionResult> {
const txId = generateTransactionId();
// Start database transaction
const dbTx = await this.db.beginTransaction();
try {
// Record transaction intent
await this.recordIntent(dbTx, txId, transaction);
// Execute the transaction
const result = await this.execute(dbTx, transaction);
// Commit database transaction
await dbTx.commit();
// Publish event (after commit for consistency)
await this.kafka.produce('pool.transactions', {
key: transaction.poolId,
value: {
txId,
type: transaction.type,
amount: transaction.amount,
status: 'COMPLETED',
timestamp: new Date(),
},
});
return { success: true, txId, result };
} catch (error) {
await dbTx.rollback();
// Record failure
await this.recordFailure(txId, error);
return { success: false, txId, error };
}
}
}
7. Prize Distribution Engine
7.1 Distribution Flow
7.2 Prize Calculation Implementation
class PrizeCalculator {
calculateDistributions(
rankings: RankedEntry[],
pool: Pool
): PrizeDistribution[] {
const { prizePool } = pool.financials;
const { payouts } = pool.distribution;
const distributions: PrizeDistribution[] = [];
for (const payout of payouts) {
// Get entries in this rank range
const entriesInRange = rankings.filter(
e => e.rank >= payout.rankFrom && e.rank <= payout.rankTo
);
if (entriesInRange.length === 0) continue;
// Calculate prize for this tier
const tierPrize = payout.type === 'FIXED'
? payout.amount
: prizePool * (payout.percentage / 100);
// Handle ties - split prize equally
const prizePerEntry = tierPrize / entriesInRange.length;
for (const entry of entriesInRange) {
distributions.push({
entryId: entry.id,
userId: entry.userId,
rank: entry.rank,
grossPrize: prizePerEntry,
tds: this.calculateTDS(prizePerEntry),
netPrize: prizePerEntry - this.calculateTDS(prizePerEntry),
status: 'PENDING',
});
}
}
return distributions;
}
private calculateTDS(amount: number): number {
// Indian TDS rules: 30% on winnings > ₹10,000
const TDS_THRESHOLD = 10000;
const TDS_RATE = 0.30;
if (amount <= TDS_THRESHOLD) return 0;
return amount * TDS_RATE;
}
}
7.3 Saga Pattern for Distribution
8. Multi-Agent Coordination
8.1 Agent Architecture for Pool Operations
8.2 Agent Implementation
interface PoolAgent {
id: string;
type: AgentType;
status: AgentStatus;
assignedPools: string[];
process(task: PoolTask): Promise<TaskResult>;
}
class DistributionAgent implements PoolAgent {
id: string;
type: AgentType = 'DISTRIBUTION';
status: AgentStatus = 'IDLE';
assignedPools: string[] = [];
private batchSize = 100;
private retryPolicy = new ExponentialBackoff();
async process(task: DistributionTask): Promise<TaskResult> {
this.status = 'PROCESSING';
const { poolId, distributions } = task;
const results: DistributionResult[] = [];
// Process in batches for efficiency
for (let i = 0; i < distributions.length; i += this.batchSize) {
const batch = distributions.slice(i, i + this.batchSize);
const batchResults = await Promise.allSettled(
batch.map(d => this.processDistribution(d))
);
results.push(...this.processBatchResults(batchResults, batch));
}
this.status = 'IDLE';
return {
poolId,
totalProcessed: distributions.length,
successful: results.filter(r => r.status === 'SUCCESS').length,
failed: results.filter(r => r.status === 'FAILED').length,
results,
};
}
private async processDistribution(
distribution: PrizeDistribution
): Promise<DistributionResult> {
return this.retryPolicy.execute(async () => {
// Credit wallet
await this.walletService.credit({
userId: distribution.userId,
amount: distribution.netPrize,
type: 'PRIZE_WINNING',
reference: distribution.id,
});
// Record in ledger
await this.ledgerService.record({
type: 'PRIZE_CREDIT',
userId: distribution.userId,
amount: distribution.netPrize,
poolId: distribution.poolId,
entryId: distribution.entryId,
});
return { status: 'SUCCESS', distributionId: distribution.id };
});
}
}
9. Guaranteed Pool Management
9.1 Guarantee Shortfall Handling
9.2 Guarantee Implementation
class GuaranteedPoolManager {
async finalizePool(poolId: string): Promise<FinalizedPool> {
const pool = await this.getPool(poolId);
if (pool.type !== 'GUARANTEED') {
return this.standardFinalize(pool);
}
const { totalCollected, platformFee } = pool.financials;
const actualPrizePool = totalCollected - platformFee;
const guaranteedAmount = pool.config.guaranteedPool!;
if (actualPrizePool >= guaranteedAmount) {
// No shortfall - standard distribution
return {
...pool,
financials: {
...pool.financials,
prizePool: actualPrizePool,
guaranteeShortfall: 0,
},
};
}
// Calculate shortfall
const shortfall = guaranteedAmount - actualPrizePool;
// Record platform liability
await this.recordPlatformLiability(poolId, shortfall);
// Adjust platform fee (may go negative)
const adjustedPlatformFee = platformFee - shortfall;
return {
...pool,
financials: {
...pool.financials,
prizePool: guaranteedAmount,
platformFee: adjustedPlatformFee,
guaranteeShortfall: shortfall,
},
};
}
}
10. Refund Processing
10.1 Refund Flow
10.2 Refund Implementation
class RefundProcessor {
async processRefunds(poolId: string): Promise<RefundResult> {
const pool = await this.poolRepository.getById(poolId);
const entries = await this.entryRepository.getByPoolId(poolId);
const refunds: Refund[] = entries.map(entry => ({
id: generateId(),
poolId,
entryId: entry.id,
userId: entry.userId,
amount: pool.config.entryFee,
status: 'PENDING',
}));
// Process refunds in parallel batches
const batchSize = 50;
const results: RefundResult[] = [];
for (let i = 0; i < refunds.length; i += batchSize) {
const batch = refunds.slice(i, i + batchSize);
const batchResults = await Promise.allSettled(
batch.map(refund => this.processRefund(refund))
);
results.push(...this.processBatchResults(batchResults));
}
return {
poolId,
totalRefunds: refunds.length,
successful: results.filter(r => r.success).length,
failed: results.filter(r => !r.success).length,
totalAmount: refunds.reduce((sum, r) => sum + r.amount, 0),
};
}
private async processRefund(refund: Refund): Promise<RefundResult> {
try {
await this.walletService.credit({
userId: refund.userId,
amount: refund.amount,
type: 'CONTEST_REFUND',
reference: refund.id,
});
await this.notificationService.send({
userId: refund.userId,
type: 'REFUND_PROCESSED',
data: { amount: refund.amount, poolId: refund.poolId },
});
return { success: true, refundId: refund.id };
} catch (error) {
return { success: false, refundId: refund.id, error };
}
}
}
11. Data Models
11.1 Entity Relationship Diagram
12. API Specifications
12.1 REST API Endpoints
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| POST | /api/v3/pools | Create pool | Admin |
| GET | /api/v3/pools/:id | Get pool details | Public |
| GET | /api/v3/pools/:id/financials | Get pool financials | Admin |
| POST | /api/v3/pools/:id/collect | Collect entry fee | Internal |
| POST | /api/v3/pools/:id/distribute | Trigger distribution | Internal |
| POST | /api/v3/pools/:id/refund | Trigger refunds | Admin |
| GET | /api/v3/pools/:id/transactions | List transactions | Admin |
| GET | /api/v3/pools/:id/distributions | List distributions | Admin |
12.2 gRPC Service Definition
service PoolService {
// Pool Management
rpc CreatePool(CreatePoolRequest) returns (Pool);
rpc GetPool(GetPoolRequest) returns (Pool);
rpc UpdatePoolStatus(UpdatePoolStatusRequest) returns (Pool);
// Fund Operations
rpc CollectEntryFee(CollectFeeRequest) returns (CollectFeeResponse);
rpc RefundEntryFee(RefundFeeRequest) returns (RefundFeeResponse);
// Distribution
rpc CalculateDistributions(CalculateRequest) returns (DistributionPlan);
rpc ExecuteDistributions(ExecuteRequest) returns (ExecuteResponse);
// Network Pools
rpc CreateNetworkPool(CreateNetworkPoolRequest) returns (NetworkPool);
rpc AggregateNetworkPool(AggregateRequest) returns (AggregatedFinancials);
// Queries
rpc GetPoolFinancials(GetFinancialsRequest) returns (PoolFinancials);
rpc ListTransactions(ListTransactionsRequest) returns (TransactionList);
}
message CollectFeeRequest {
string pool_id = 1;
string user_id = 2;
string entry_id = 3;
int64 amount = 4;
string idempotency_key = 5;
}
message DistributionPlan {
string pool_id = 1;
int64 total_prize_pool = 2;
int64 platform_fee = 3;
repeated PrizeDistribution distributions = 4;
}
13. Monitoring & Observability
13.1 Key Metrics
const poolMetrics = {
// Financial Metrics
totalCollected: new Gauge({
name: 'pool_total_collected',
help: 'Total funds collected',
labelNames: ['pool_type'],
}),
totalDistributed: new Gauge({
name: 'pool_total_distributed',
help: 'Total prizes distributed',
labelNames: ['pool_type'],
}),
platformRevenue: new Gauge({
name: 'pool_platform_revenue',
help: 'Platform fee revenue',
labelNames: ['pool_type'],
}),
// Transaction Metrics
transactionLatency: new Histogram({
name: 'pool_transaction_latency_seconds',
help: 'Transaction processing latency',
labelNames: ['type'],
buckets: [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5],
}),
transactionFailures: new Counter({
name: 'pool_transaction_failures_total',
help: 'Failed transactions',
labelNames: ['type', 'reason'],
}),
// Distribution Metrics
distributionDuration: new Histogram({
name: 'pool_distribution_duration_seconds',
help: 'Time to complete distribution',
buckets: [1, 5, 10, 30, 60, 120, 300],
}),
};
13.2 Alert Rules
groups:
- name: pool-manager-alerts
rules:
- alert: HighTransactionFailureRate
expr: rate(pool_transaction_failures_total[5m]) > 0.01
for: 2m
labels:
severity: critical
annotations:
summary: "Transaction failure rate > 1%"
- alert: DistributionStuck
expr: pool_status{status="DISTRIBUTING"} > 0
and time() - pool_status_changed_timestamp > 1800
for: 5m
labels:
severity: critical
annotations:
summary: "Pool stuck in DISTRIBUTING for > 30 minutes"
- alert: BalanceMismatch
expr: abs(pool_collected - pool_distributed - pool_pending) > 100
for: 1m
labels:
severity: critical
annotations:
summary: "Pool balance reconciliation mismatch"
14. Security & Compliance
14.1 Financial Controls
14.2 Audit Trail Implementation
class AuditLogger {
async logTransaction(
transaction: PoolTransaction,
actor: Actor,
context: AuditContext
): Promise<void> {
const auditEntry: AuditEntry = {
id: generateId(),
timestamp: new Date(),
entityType: 'POOL_TRANSACTION',
entityId: transaction.id,
action: transaction.type,
actor: {
id: actor.id,
type: actor.type,
ip: context.ip,
userAgent: context.userAgent,
},
before: context.previousState,
after: transaction,
metadata: {
poolId: transaction.poolId,
amount: transaction.amount,
correlationId: context.correlationId,
},
};
// Write to immutable audit log
await this.auditStore.append(auditEntry);
// Publish for real-time monitoring
await this.kafka.produce('audit.pool.transactions', auditEntry);
}
}
15. Testing Strategy
15.1 Financial Accuracy Tests
describe('Pool Manager Financial Tests', () => {
describe('Prize Distribution', () => {
it('should distribute exact prize pool amount', async () => {
const pool = await createTestPool({
entryFee: 100,
entries: 1000,
platformFeePct: 10,
});
const distributions = await poolManager.calculateDistributions(pool.id);
const totalDistributed = distributions.reduce(
(sum, d) => sum + d.grossPrize, 0
);
expect(totalDistributed).toBe(pool.financials.prizePool);
});
it('should handle ties correctly', async () => {
const pool = await createTestPool({ entries: 100 });
// Create tie at rank 1
await createTiedEntries(pool.id, [
{ userId: 'user1', score: 100 },
{ userId: 'user2', score: 100 },
]);
const distributions = await poolManager.calculateDistributions(pool.id);
const rank1Prizes = distributions.filter(d => d.rank === 1);
expect(rank1Prizes).toHaveLength(2);
expect(rank1Prizes[0].grossPrize).toBe(rank1Prizes[1].grossPrize);
});
});
describe('Guaranteed Pool', () => {
it('should cover shortfall from platform', async () => {
const pool = await createTestPool({
type: 'GUARANTEED',
guaranteedPool: 100000,
entries: 50, // Not enough to cover guarantee
entryFee: 100,
});
const finalized = await poolManager.finalizePool(pool.id);
expect(finalized.financials.prizePool).toBe(100000);
expect(finalized.financials.guaranteeShortfall).toBeGreaterThan(0);
});
});
});
16. Future Enhancements
16.1 Roadmap
| Phase | Feature | Description |
|---|---|---|
| Q1 2026 | Dynamic Prize Structures | AI-optimized prize distributions |
| Q2 2026 | Cross-Currency Pools | Multi-currency support |
| Q3 2026 | Instant Settlements | Real-time prize credits |
| Q4 2026 | Blockchain Audit | Immutable transaction records |
17. Appendix
17.1 Glossary
| Term | Definition |
|---|---|
| Prize Pool | Total funds available for distribution |
| Platform Fee | Commission taken by the platform |
| TDS | Tax Deducted at Source (Indian tax) |
| Shortfall | Gap between collected and guaranteed amount |
17.2 References
Document Version: 3.0.0 | Last Updated: January 2026