feat(affiliate): partners see LIFETIME referred volume; regular keep this-cycle#569
Conversation
…iates keep this-cycle Per product: "Referred volume" is the current payout cycle, except for Partners, where it is lifetime. Backend getReferrerStats now computes BOTH figures in one pass (the cycle bound moves into a FILTER on the SUM, so the lifetime SUM sees every post-bound referred trade) and returns currentCycleVolumeUsd + lifetimeReferredVolumeUsd. The Partner dashboard and the Profile partner summary now display lifetimeReferredVolumeUsd labelled "lifetime"; the regular self-serve surface still shows currentCycleVolumeUsd "this cycle". Deploy-order safe: an older FE reading the new field, or a newer FE before the backend deploys, both degrade to $0 (formatUsd handles undefined), never a crash. rebate-indexer typecheck + 16 tests green; cowswap typecheck green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 48760f3fed
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| COALESCE(SUM(t.value_usd), 0)::text AS lifetime_volume_usd | ||
| FROM referrals r | ||
| LEFT JOIN trades t | ||
| ON t.wallet = r.referred_wallet | ||
| AND t.block_timestamp >= ${start.toISOString()} AND t.block_timestamp < ${end.toISOString()} | ||
| AND t.block_timestamp >= r.bound_at AND t.value_usd IS NOT NULL |
There was a problem hiding this comment.
Avoid lifetime scans for regular affiliate stats
For regular referrers on the public /affiliate/:wallet path, kind is known before this query but the join now includes every post-bind trade so the new lifetime_volume_usd can be computed, while the regular UI still only consumes the current-cycle value. For a referrer with long-lived referred wallets, each profile refresh/rate-limited public request now aggregates all historical trades instead of the previous bounded month window; consider keeping the month-bounded join when kind !== 'partner' or computing the lifetime aggregate only for partner callers.
Useful? React with 👍 / 👎.
1. Lifetime referee scan on the public regular path (#569). getReferrerStats moved the cycle window from the JOIN into a FILTER so it could also SUM lifetime, which made every public, rate-limited /affiliate/:wallet request scan a regular referrer's full post-bound trade history even though the regular UI only displays the current-cycle figure. Branch the query on kind: regular keeps the cycle-bounded JOIN (scans only this cycle); partners get the widened lifetime scan they actually display. 2. AbortSignal.timeout unsupported on older targets (#568). The production browserslist still includes iOS Safari 15.x, which lacks AbortSignal.timeout; the helper threw before fetch ran, surfacing the signed partner-dashboard / code-creation / bind POSTs as network failures. Feature-detect and fall back to AbortController + setTimeout. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Per your directive: Referred volume = current cycle, except Partners (lifetime).
getReferrerStats): computes both figures in one query — the cycle bound moves into aFILTERon theSUMso the lifetimeSUMsees every post-bound referred trade. ReturnscurrentCycleVolumeUsd+lifetimeReferredVolumeUsd(flows to both/affiliate/:walletand/partner)./#/partner) and the Profile partner summary now showlifetimeReferredVolumeUsdlabelled "lifetime"; the regular self-serve surface keepscurrentCycleVolumeUsd"this cycle".Deploy-order safe (old FE / new field or new FE / old backend → $0, never a crash). typecheck + 16 tests green; cowswap typecheck green; em-dash 0.
🤖 Generated with Claude Code