Skip to main content

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

MethodEndpointDescriptionAuth
POST/api/v3/poolsCreate poolAdmin
GET/api/v3/pools/:idGet pool detailsPublic
GET/api/v3/pools/:id/financialsGet pool financialsAdmin
POST/api/v3/pools/:id/collectCollect entry feeInternal
POST/api/v3/pools/:id/distributeTrigger distributionInternal
POST/api/v3/pools/:id/refundTrigger refundsAdmin
GET/api/v3/pools/:id/transactionsList transactionsAdmin
GET/api/v3/pools/:id/distributionsList distributionsAdmin

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

PhaseFeatureDescription
Q1 2026Dynamic Prize StructuresAI-optimized prize distributions
Q2 2026Cross-Currency PoolsMulti-currency support
Q3 2026Instant SettlementsReal-time prize credits
Q4 2026Blockchain AuditImmutable transaction records

17. Appendix

17.1 Glossary

TermDefinition
Prize PoolTotal funds available for distribution
Platform FeeCommission taken by the platform
TDSTax Deducted at Source (Indian tax)
ShortfallGap between collected and guaranteed amount

17.2 References


Document Version: 3.0.0 | Last Updated: January 2026