Skip to content

ReferralManager

Manages the referrer side of the referral system: code generation, referral tracking, eligibility validation, rebate accounting, and monthly statistics.

High-Level Design

Referral Code Generation

Codes follow the format ACE-{FIRST5}-{LAST3} derived from the user's wallet address (e.g., 0x1234...333ACE-0X123-333). Generation is delegated to PromoCodeRegistry DO which handles global uniqueness and collision resolution by appending -1, -2 suffixes. The call is idempotent — requesting a code for the same wallet returns the existing one.

Generated codes are stored centrally in PromoCodeRegistry (not locally). They grant referees a 10% discount on their first billing cycle for Standard/Pro plans.

Cross-DO Referral Recording

The core operation is a cross-DO call initiated by the referee's SubscriptionManager:

Referee's SubscriptionManager.createSubscription()
  → PromoCodeRegistry.lookupReferrer(code) → gets referrer userId
  → Referrer's DO.recordReferral({ refereeWallet, plan, subscriptionTimestamp, ... })
  → ReferralManager validates eligibility, stores record, updates monthly stats

This call is best-effort — errors are logged but never fail the referee's subscription. The referee's subscription succeeds regardless of whether the referral is recorded.

Point-in-Time Eligibility Validation

Eligibility is evaluated at the exact timestamp of the referee's subscription, not at current time. This ensures fairness when referrer status changes between referral sharing and redemption.

Three checks must pass:

  1. Referee is first-time subscriber — no prior subscription records exist
  2. Referrer has an active subscription at that timestamp — queried via getSubscriptionAtTime()
  3. Referrer's plan qualifies — must be Standard, Pro, or Sponsored

"Active" includes subscribed, wind-down, and grace-period statuses — only expired disqualifies. Sponsored subscriptions bypass the plan check entirely.

Failed validation still creates a record with rebate_status = "ineligible" and the specific reason, preserving a complete audit trail.

Dynamic Rebate Amounts

Rebate amounts are determined dynamically at recording time by calling PromoCodeRegistry.calculateRebateAmounts(). The registry resolves the active rebate program using priority: Custom KOL program > Global tier program > Legacy defaults.

Tiered programs set rebate amounts based on the referrer's previous calendar month referral count (from promo_code_usages). For example, a program might pay $5/$50/$80 for 0–9 referrals/month and $8/$80/$120 for 10+ referrals/month.

If the PromoCodeRegistry RPC fails, the system falls back to legacy defaults ($4/$40/$68) and logs an error — referral recording is never blocked by a registry outage.

See PromoCodeRegistry for program management details.

Rebate Accounting (Tracking-Only)

Rebates are permanent once eligible — no clawback on cancellation. The system is tracking-only: rebates are recorded and aggregated but never automatically credited to the referrer's trading balance. Actual payout happens externally via updateRebateStatus().

Status lifecycle: eligible or ineligible (set at recording) → paid (set by admin).

Stats Response with Live Program Info

getReferralStatsResponse() is async — it calls PromoCodeRegistry.calculateRebateAmounts() at current time to include the referrer's active program, current tier rebate amounts, and last month's referral count in the response. This lets the frontend display what the referrer would earn on their next referral. On RPC failure, it falls back to legacy amounts with error logging.

Monthly Stats Denormalization

Each recordReferral() upserts a referral_monthly_stats row keyed by YYYY-MM period, tracking per-plan counts and rebate totals. This avoids expensive aggregation queries for analytics.

Idempotency

A UNIQUE constraint on referee_user_id prevents duplicate referrals. If recordReferral() is called again for the same referee, it returns the existing record without modification.

See Also