Skip to content

feat(scripts): affiliate monthly USDC payout generator - #48

Merged
kaladinlight merged 6 commits into
developfrom
feat/affiliate-monthly-payout-script
Jul 2, 2026
Merged

feat(scripts): affiliate monthly USDC payout generator#48
kaladinlight merged 6 commits into
developfrom
feat/affiliate-monthly-payout-script

Conversation

@kaladinlight

@kaladinlight kaladinlight commented Jul 2, 2026

Copy link
Copy Markdown
Member

Description

Adds a script that generates monthly affiliate USDC payouts from swap-fee attribution, plus a supporting swap-service fee-math tweak.

  • Aggregates SUCCESS swaps with a partnerCode over a calendar month, summing each partner's earned share of the verified on-chain affiliate fee (via the injected swap-service fee math) — never the bps-implied estimate.
  • Partitions non-payable swaps into labeled review buckets (unverified, noAffiliateFee, unpriceable, unresolvedFee, fee anomaly, partnerBpsUnset) so the output explains why each swap was excluded.
  • Guards the maya/woody fee-mislabel case with a deviation check between the on-chain fee and the bps-implied estimate.
  • Resolves each partner's payout address (receiveAddress, falling back to walletAddress), checksums it, and excludes non-EVM/zero addresses. Partner-code matching is case-insensitive to match the DB citext column.
  • Emits a Safe airdrop CSV (Arbitrum USDC) and a JSON payout record with totals + per-swap warnings.
  • CLI: yarn affiliate-payouts generate [YYYY-MM] (defaults to the previous calendar month, UTC), --force to overwrite existing artifacts.
  • swap-service calculateFeeForSwap: prefer the on-chain fee, fall back to the bps-implied fee, and skip only when neither is available (previously defaulted feeUsd to 0).

Testing

  • yarn affiliate-payouts:test — 25 passing unit tests covering month-window resolution, per-partner aggregation/classification, the fee-anomaly guard, USDC formatting, address normalization, the Safe CSV output, and payout-record assembly.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a new monthly affiliate payout workflow that generates payout CSV and JSON artifacts.
    • Introduced a payout summary that tracks totals, exclusions, warnings, and recipient eligibility.
    • Added supporting scripts and test setup for running and validating the payout process.
  • Bug Fixes

    • Improved fee calculations to surface both on-chain and implied fee values for more accurate payout checks.
    • Added address validation and payout-file overwrite protection.
  • Tests

    • Added coverage for payout window parsing, fee anomaly detection, formatting, address checks, CSV output, and payout aggregation.

kaladinlight and others added 4 commits June 30, 2026 14:59
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Aggregates verified successful swaps by partnerCode for a month window and
emits a Gnosis Safe CSV-airdrop file (USDC on Arbitrum) plus a JSON run record.

Uses the on-chain verified affiliate fee, guarded by a deviation check against
the bps-implied fee (volume x verifiedBps): swaps whose on-chain fee is wildly
off (e.g. MayaChain rows that label the fee asset USDC but collect CACAO) are
flagged and excluded rather than paid. Extends calculateFeeForSwap to expose
actualFeeUsd + impliedFeeUsd (additive, backward-compatible) so the guard can
compare them.

Run: yarn affiliate-payouts generate [startDate] [endDate]
Test: yarn affiliate-payouts:test

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Split scripts/affiliate-payouts.ts into scripts/affiliate-payouts/ with
affiliate-payouts.ts (CLI), utils.ts (logic), types.ts, and tests.

- resolveWindow takes a YYYY-MM month, defaulting to the previous calendar
  month; drops the start/end ISO date pair
- Clearer excluded-swap category names (unpriceable / feeAnomaly /
  unresolvedFee) and a rewritten classification comment
- buildPayouts takes a pre-lowercased affiliate map (caller owns citext
  normalization); normalizeRecipient renamed to normalizeAddress
- swap-service calculateFeeForSwap: prefer the on-chain fee, fall back to
  the bps-implied fee, and skip only when neither is available (no longer
  defaults feeUsd to 0)
- add scripts/tsconfig.json, move ts-jest isolatedModules into it, and
  update the yarn affiliate-payouts path

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kaladinlight, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 15 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1f44996e-43b7-4dab-bcdd-265eb7b225da

📥 Commits

Reviewing files that changed from the base of the PR and between 46057f0 and ad0c11b.

📒 Files selected for processing (4)
  • .gitignore
  • apps/swap-service/src/swaps/utils.ts
  • package.json
  • tsconfig.json
📝 Walkthrough

Walkthrough

Adds a monthly affiliate USDC payout script (scripts/affiliate-payouts) with types, pure aggregation/formatting utilities, a CLI generator, and unit tests; extends calculateFeeForSwap with actualFeeUsd/impliedFeeUsd; adds a design doc; and updates .gitignore, package.json, and TypeScript/Jest configs accordingly.

Changes

Affiliate Monthly Payout Feature

Layer / File(s) Summary
Swap fee attribution
apps/swap-service/src/swaps/utils.ts
calculateFeeForSwap now returns actualFeeUsd and impliedFeeUsd alongside feeUsd, volumeUsd, verifiedBps, deriving feeUsd by preferring the actual on-chain fee and falling back to the implied fee.
Design specification
docs/superpowers/specs/2026-06-30-affiliate-monthly-usdc-payout-design.md
New design doc covering payout window logic, fee-deviation guard, recipient/address validation, CSV/JSON output artifacts, module split, and test plan.
Payout data types
scripts/affiliate-payouts/types.ts
Defines PartnerAccrual, PartnerPayout, PayoutWindow, FeeResult, FeeAnomaly, swap classification types, AggregateResult, FeeDeps, PayoutWarning, and PayoutRecord.
Core aggregation and payout utilities + tests
scripts/affiliate-payouts/utils.ts, scripts/affiliate-payouts/affiliate-payouts.test.ts
Implements resolveWindow, checkFeeAnomaly, aggregateByPartner, formatUsdc, normalizeAddress, toCsv, buildPayouts, buildRecord, with unit tests covering windowing, aggregation, anomalies, formatting, and record building.
CLI entry script and wiring
scripts/affiliate-payouts/affiliate-payouts.ts, package.json, .gitignore
Adds generate/main CLI with summary printing and artifact writing (CSV/JSON), new affiliate-payouts npm scripts, and .gitignore rule for /payouts/.
TypeScript/Jest project config
scripts/jest.config.ts, scripts/tsconfig.json, tsconfig.json
New Jest config and scripts-specific tsconfig, and a broadened root tsconfig include glob covering all scripts.

Estimated code review effort: 3 (Moderate) | ~35 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI as main (CLI)
  participant Generate as generate()
  participant DB as Prisma/DB
  participant Aggregate as aggregateByPartner
  participant Artifacts as writeArtifacts

  CLI->>Generate: resolveWindow(month)
  Generate->>DB: query swaps + affiliates
  DB-->>Generate: swap rows, affiliate rows
  Generate->>Aggregate: aggregate swaps by partner
  Aggregate-->>Generate: totals, anomalies, warnings
  Generate->>Artifacts: buildPayouts + buildRecord
  Artifacts-->>Generate: CSV + JSON written
  Generate->>CLI: printSummary
Loading

Possibly related PRs

  • shapeshift/microservices#31: Both PRs change how actual affiliate fee data is derived and used, affecting calculateFeeForSwap outputs consumed by payout calculations.
  • shapeshift/microservices#35: Both PRs modify apps/swap-service/src/swaps/utils.ts fee computation logic around verified fee/volume attribution.

Poem

A rabbit counts the fees each month,
Actual and implied, side by side,
Hop, hop, into a tidy CSV,
Warnings flagged, no partner left behind,
Thump! The payout ledger's verified. 🥕📊

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: a new affiliate monthly USDC payout generator in scripts.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/affiliate-monthly-payout-script

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/jest.config.ts (1)

1-12: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Align ts-jest with Jest 30
ts-jest@29.2.5 doesn’t support Jest 30. Upgrade ts-jest to a release that includes peerDependencies: jest "^29.0.0 || ^30.0.0" (or pin Jest to 29.x) to avoid install/runtime mismatches.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/jest.config.ts` around lines 1 - 12, The Jest config in config should
be aligned with a ts-jest version that supports Jest 30; either upgrade the
ts-jest dependency to a release with peer support for Jest 30 or pin Jest back
to 29.x. Check the test setup around the Config export in jest.config.ts and
update the package dependency versions so the transform using ts-jest matches
the installed Jest major version.
🧹 Nitpick comments (2)
scripts/affiliate-payouts/types.ts (1)

20-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider deriving FeeResult from calculateFeeForSwap's return type instead of duplicating it.

FeeResult manually mirrors the shape returned by apps/swap-service/src/swaps/utils.ts's calculateFeeForSwap. Since these two aren't linked, a future change to that function's return shape can silently drift out of sync with this type (no compile error would surface it). A type-only import (import type) doesn't pull in the ESM-only runtime deps the design doc is trying to avoid, so this is compile-time-only and safe for the "pure lib" goal.

♻️ Suggested refactor
-import type { Swap as PrismaSwap } from '`@prisma/client`'
+import type { Swap as PrismaSwap } from '`@prisma/client`'
+import type { calculateFeeForSwap } from '../../apps/swap-service/src/swaps/utils'
 import type BigNumber from 'bignumber.js'

-export type FeeResult = {
-  feeUsd: number
-  volumeUsd: number
-  verifiedBps: number
-  actualFeeUsd: number | null
-  impliedFeeUsd: number | null
-}
+export type FeeResult = NonNullable<ReturnType<typeof calculateFeeForSwap>>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/affiliate-payouts/types.ts` around lines 20 - 26, `FeeResult` is
manually duplicating the return shape of `calculateFeeForSwap`, so update the
type in `scripts/affiliate-payouts/types.ts` to derive from that function’s
return type instead of maintaining a separate interface. Use a type-only
reference to `calculateFeeForSwap` from `apps/swap-service/src/swaps/utils` so
the pure-lib build still avoids runtime ESM dependencies, and keep any existing
consumers of `FeeResult` working through the derived alias.
docs/superpowers/specs/2026-06-30-affiliate-monthly-usdc-payout-design.md (1)

190-200: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Design doc is stale vs. the final module structure/field names.

The JSON example totals use skippedSwaps/anomalousSwaps, and the module-structure section references a single scripts/affiliate-payouts-lib.ts file with a noVerifiedFee field. The actual implementation (per scripts/affiliate-payouts/types.ts) uses unpriceableSwaps/feeAnomalySwaps/unresolvedFee and splits into scripts/affiliate-payouts/{types.ts, utils.ts, affiliate-payouts.ts}. Since this design doc is the reference for the JSON payout-record schema, worth syncing it with the shipped names/layout to avoid confusing future readers of the settlement-tracking follow-up work.

Also applies to: 227-247

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-06-30-affiliate-monthly-usdc-payout-design.md`
around lines 190 - 200, Update the design doc’s payout schema and
module-structure references to match the shipped affiliate payouts
implementation: replace the stale totals field names like skippedSwaps and
anomalousSwaps with the actual names used in scripts/affiliate-payouts/types.ts
such as unpriceableSwaps, feeAnomalySwaps, and unresolvedFee, and revise the
module layout to reflect scripts/affiliate-payouts/{types.ts, utils.ts,
affiliate-payouts.ts} instead of a single scripts/affiliate-payouts-lib.ts file.
Keep the JSON example and any schema prose consistent with the identifiers in
the affiliate-payouts types so the reference matches the real record shape.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/affiliate-payouts/utils.ts`:
- Around line 199-222: Zero/negative-fee accruals are being filtered out in
buildPayouts before they can appear in the payout record. Update buildPayouts so
accruals with feesEarnedUsd <= 0 are still emitted as PartnerPayout entries,
marked included: false with a clear excludedReason, or routed into a dedicated
review bucket, instead of being skipped. Keep the existing affiliate
lookup/normalizeAddress logic intact and make sure buildRecord can surface these
entries in the final audit trail alongside the other buckets.

---

Outside diff comments:
In `@scripts/jest.config.ts`:
- Around line 1-12: The Jest config in config should be aligned with a ts-jest
version that supports Jest 30; either upgrade the ts-jest dependency to a
release with peer support for Jest 30 or pin Jest back to 29.x. Check the test
setup around the Config export in jest.config.ts and update the package
dependency versions so the transform using ts-jest matches the installed Jest
major version.

---

Nitpick comments:
In `@docs/superpowers/specs/2026-06-30-affiliate-monthly-usdc-payout-design.md`:
- Around line 190-200: Update the design doc’s payout schema and
module-structure references to match the shipped affiliate payouts
implementation: replace the stale totals field names like skippedSwaps and
anomalousSwaps with the actual names used in scripts/affiliate-payouts/types.ts
such as unpriceableSwaps, feeAnomalySwaps, and unresolvedFee, and revise the
module layout to reflect scripts/affiliate-payouts/{types.ts, utils.ts,
affiliate-payouts.ts} instead of a single scripts/affiliate-payouts-lib.ts file.
Keep the JSON example and any schema prose consistent with the identifiers in
the affiliate-payouts types so the reference matches the real record shape.

In `@scripts/affiliate-payouts/types.ts`:
- Around line 20-26: `FeeResult` is manually duplicating the return shape of
`calculateFeeForSwap`, so update the type in
`scripts/affiliate-payouts/types.ts` to derive from that function’s return type
instead of maintaining a separate interface. Use a type-only reference to
`calculateFeeForSwap` from `apps/swap-service/src/swaps/utils` so the pure-lib
build still avoids runtime ESM dependencies, and keep any existing consumers of
`FeeResult` working through the derived alias.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 274f55f0-b64a-47e9-b021-58074e34557f

📥 Commits

Reviewing files that changed from the base of the PR and between 8b24430 and 46057f0.

📒 Files selected for processing (11)
  • .gitignore
  • apps/swap-service/src/swaps/utils.ts
  • docs/superpowers/specs/2026-06-30-affiliate-monthly-usdc-payout-design.md
  • package.json
  • scripts/affiliate-payouts/affiliate-payouts.test.ts
  • scripts/affiliate-payouts/affiliate-payouts.ts
  • scripts/affiliate-payouts/types.ts
  • scripts/affiliate-payouts/utils.ts
  • scripts/jest.config.ts
  • scripts/tsconfig.json
  • tsconfig.json

Comment thread scripts/affiliate-payouts/utils.ts
kaladinlight and others added 2 commits July 2, 2026 13:40
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- add jest to root tsconfig types; remove scripts/tsconfig.json (scripts
  don't emit, so the per-workspace tsconfig split isn't needed — jest
  types belong at root)
- remove dead root package.json jest block (matched no *.spec.ts and was
  never read; every jest call passes --config)
- trim payouts gitignore + swap-service fee-calc comments

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kaladinlight
kaladinlight merged commit 6650696 into develop Jul 2, 2026
2 checks passed
@kaladinlight
kaladinlight deleted the feat/affiliate-monthly-payout-script branch July 2, 2026 19:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant