Skip to content

feat(swap-service): explicit bps split, partner rename, and fee math fix#41

Open
kaladinlight wants to merge 3 commits into
developfrom
feat/swap-bps-and-partner-fields
Open

feat(swap-service): explicit bps split, partner rename, and fee math fix#41
kaladinlight wants to merge 3 commits into
developfrom
feat/swap-bps-and-partner-fields

Conversation

@kaladinlight
Copy link
Copy Markdown
Member

@kaladinlight kaladinlight commented May 28, 2026

Summary

  • Explicit bps split: Break out fee tracking into three fields (affiliateBps, shapeshiftBps, partnerBps) so the invariant affiliateBps = shapeshiftBps + partnerBps holds without derivation. Callers now pass all three values snapshotted at quote time to eliminate drift between quote and registration.
  • Rename affiliateAddresspartnerAddress: Distinguishes the partner's payout address from on-chain affiliate addresses found during verification. Update all service code, controllers, queries, and test fixtures.
  • Require receiveAddress and buyAccountId: Both are always known at swap registration time. Migration backfills buyAccountId from receiveAddress.
  • Remove account hashing: hashAccountId removed from swap service — raw addresses are already stored alongside (receiveAddress, partnerAddress), making the hashing pointless. Also fixes the checkTradeStatus bug where a hashed value was passed to swappers as an address.
  • Partner-first fee math: Rename getAffiliateFeeRategetPartnerFeeRate. Partner gets their agreed partnerBps share first (capped at 100%), ShapeShift absorbs any shortfall if on-chain bps is less than expected.
  • Verifier cleanup: Remove redundant ?? undefined on affiliateBps (now always non-null), simplify hasAffiliate checks.

Testing

  • Run migration against backup of production DB
  • Verify bps invariant holds for backfilled data (affiliateBps = shapeshiftBps + partnerBps)
  • Verify partner fee calculations match expected values for partner and non-partner swaps
  • Coordinate with web repo changes (TODOs in tradeExecution.ts and useAffiliateTracking.ts)
  • Verify all existing tests pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor

    • Restructured affiliate and fee calculation logic to partner-based terminology.
    • API endpoint parameters renamed from affiliateAddress to address.
    • Updated response structures with new partner-related fields and fee calculations.
    • Database schema updated to enforce required fields and restructure partner/affiliate data.
  • Breaking Changes

    • buyAccountId and receiveAddress now required in swap requests.
    • Fee calculation methodology updated.

Review Change Stack

kaladinlight and others added 3 commits May 27, 2026 11:46
…Address rename

Break out fee tracking into three explicit fields (affiliateBps, shapeshiftBps,
partnerBps) so the invariant affiliateBps = shapeshiftBps + partnerBps holds
without derivation. Rename affiliateAddress → partnerAddress to distinguish the
partner's payout address from on-chain affiliate addresses in verification.

- Add partnerBps column with @default(0), make receiveAddress required
- Migration backfills partnerBps from existing data, fixes shapeshiftBps for
  non-partner swaps, renames column + index
- Callers now pass all three bps values (snapshotted at quote time) to eliminate
  drift between quote and registration
- Update DTO, service, controller, affiliate service, and test fixtures

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Make buyAccountId required — the receive address is always the buy account.
Remove hashAccountId from swap service since raw addresses are already stored
alongside (receiveAddress, partnerAddress) making the hashing pointless.

- buyAccountId: String? → String in schema, migration backfills from receiveAddress
- Remove hashAccountId usage from createSwap and getSwapsByAccountId
- Update test fixtures with non-null buyAccountId

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rename getAffiliateFeeRate → getPartnerFeeRate with partner-first semantics:
partner gets their agreed partnerBps share first (capped at 100%), ShapeShift
absorbs the shortfall if on-chain bps is less than expected.

- Use min(partnerBps / verifiedBps, 1) instead of (verifiedBps - shapeshiftBps) / verifiedBps
- Clean up verifiers: remove redundant ?? undefined on affiliateBps (now always non-null)
- Simplify hasAffiliate checks (affiliateBps > 0 instead of !== undefined && > 0)
- Update test fixtures with raw addresses instead of hashes

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

coderabbitai Bot commented May 28, 2026

📝 Walkthrough

Walkthrough

This PR refactors swap service addressing and fee models from affiliate-based to partner-based semantics. It updates data contracts, database schema, and core service logic to resolve, store, and aggregate swap fees using partner address and partner basis points alongside a fixed ShapeShift basis points allocation.

Changes

Swap Service Partner Migration

Layer / File(s) Summary
Data Contracts and Schema
packages/shared-types/src/index.ts, prisma/schema/swap-service.prisma, prisma/migrations/20260527000000_swap_bps_and_partner_fields/migration.sql
CreateSwapDto requires buyAccountId, receiveAddress, and affiliateBps, adds required shapeshiftBps, removes affiliateAddress, and introduces optional partnerAddress and partnerBps. Prisma schema renames affiliateAddress to partnerAddress, adds partnerBps with default 0, makes several fields non-optional, and updates indexing. Migration backfills data to enforce new constraints and field relationships.
Constants and Fee Calculation Utilities
apps/swap-service/src/swaps/constants.ts, apps/swap-service/src/swaps/utils.ts
New constants file exports SHAPESHIFT_BPS (10) and REFERRER_FEE_RATE (0.1). New getPartnerFeeRate(verifiedBps, partnerBps) utility replaces getAffiliateFeeRate, computing min(partnerBps / verifiedBps, 1) or 0 when verifiedBps <= 0.
SwapsService Swap Creation and Querying
apps/swap-service/src/swaps/swaps.service.ts
createSwap resolves partnerAddress instead of affiliateAddress, persists partnerAddress and partnerBps fields, and removes account ID hashing. Imports REFERRER_FEE_RATE constant. Removes static fee constants. getSwapsByAccountId queries raw account IDs without hashing. Helper renamed to resolvePartnerAddress.
SwapsService Fee Aggregation
apps/swap-service/src/swaps/swaps.service.ts
calculateAffiliateFees method accepts address parameter, filters swaps by partnerAddress, computes fee rate via getPartnerFeeRate(fee.verifiedBps, swap.partnerBps), and uses imported REFERRER_FEE_RATE constant for referral calculations.
AffiliateService Partner Queries
apps/swap-service/src/affiliate/affiliate.service.ts
getAffiliateStats and getAffiliateSwaps methods accept address parameter and query by partnerAddress instead of affiliateAddress. Fee calculation uses getPartnerFeeRate(..., swap.partnerBps). resolvePartnerCode returns { partnerBps, partnerCode, partnerAddress, shapeshiftBps } and lookupAffiliateBps method removed.
Controller Endpoint Updates
apps/swap-service/src/swaps/swaps.controller.ts, apps/swap-service/src/affiliate/affiliate.controller.ts
SwapsController.getAffiliateFees route parameter renamed from :affiliateAddress to :address. AffiliateController endpoint transforms response to return partnerBps (renamed from bps) and adds shapeshiftBps field sourced from SHAPESHIFT_BPS constant.
Swap Verification Service
apps/swap-service/src/verification/swap-verification.service.ts
Updates affiliate detection across all verification paths (verifyPortals, verifyButterSwap, verifyCetus, verifySunio, verifyAvnu, verifyStonfi, verifyAcross) to assign affiliateBps directly from swap.affiliateBps, simplify hasAffiliate to numeric comparison (affiliateBps > 0), and use swap.partnerAddress instead of swap.affiliateAddress where applicable.
Test Fixtures
apps/swap-service/src/verification/__tests__/fixtures/maya/swap.ts, apps/swap-service/src/verification/__tests__/fixtures/near/swap.ts, apps/swap-service/src/verification/__tests__/fixtures/relay/swap.ts, apps/swap-service/src/verification/__tests__/fixtures/thorchain/swap.ts
Updates all swap fixtures to populate both sellAccountId and buyAccountId with valid addresses (previously buyAccountId was null), add partnerAddress and partnerBps: 50 fields, and adjust shapeshiftBps values to reflect new fee distribution.

🎯 4 (Complex) | ⏱️ ~45 minutes

🐰 A swap-service journey through partners old,
Affiliate fields traded for addresses bold,
Fee rates calculated in new fashion ways,
Partner and ShapeShift split the trading bays,
Schema and service sing together in tune!

🚥 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 accurately captures the three main changes: explicit basis-point field split (affiliateBps, shapeshiftBps, partnerBps), renaming affiliateAddress to partnerAddress, and fixing fee calculation semantics.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/swap-bps-and-partner-fields

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
apps/swap-service/src/verification/swap-verification.service.ts (1)

320-327: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Only return fee details for verified ShapeShift Portals orders.

When context.partner is present but not our treasury, this branch still returns swap.affiliateBps and feeAmount. That records a third-party partner fee inside a hasAffiliate: false result.

Suggested change
     return {
       verificationStatus: 'SUCCESS',
       hasAffiliate: hasShapeshiftAffiliate,
-      affiliateBps: swap.affiliateBps,
+      affiliateBps: hasShapeshiftAffiliate ? swap.affiliateBps : undefined,
       affiliateAddress: hasShapeshiftAffiliate ? expectedTreasuryAddress : undefined,
       verifiedSellAmountCryptoBaseUnit,
       actualBuyAmountCryptoBaseUnit: undefined,
-      actualAffiliateFeeAmountCryptoBaseUnit: orderData?.context?.feeAmount,
+      actualAffiliateFeeAmountCryptoBaseUnit: hasShapeshiftAffiliate
+        ? orderData?.context?.feeAmount
+        : undefined,
     }
🤖 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 `@apps/swap-service/src/verification/swap-verification.service.ts` around lines
320 - 327, The returned object is including affiliate fields even when
hasShapeshiftAffiliate is false; update the return in the verification result so
that swap.affiliateBps, affiliateAddress (expectedTreasuryAddress) and
actualAffiliateFeeAmountCryptoBaseUnit (orderData?.context?.feeAmount) are only
set when hasShapeshiftAffiliate is true—otherwise omit or set them to undefined
and ensure hasAffiliate reflects hasShapeshiftAffiliate; locate the return
object in the function that computes hasShapeshiftAffiliate and adjust those
properties conditionally.
🤖 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 `@apps/swap-service/src/affiliate/affiliate.service.ts`:
- Around line 151-155: The return currently exposes the legacy total fee
(affiliate.bps) as partnerBps; change resolvePartnerCode() to compute the
partner's share by subtracting the SHAPESHIFT_BPS from the stored affiliate.bps
(e.g., partnerBps = Math.max(0, affiliate.bps - SHAPESHIFT_BPS)) so callers get
the partner portion, not the unsplit total; keep
createAffiliate()/updateAffiliate() storing the single legacy bps field but
ensure any returned partnerBps uses this subtraction and guards against negative
values.

In `@apps/swap-service/src/swaps/swaps.service.ts`:
- Around line 220-221: getSwapsByAccountId currently only matches raw
sellAccountId/buyAccountId which drops legacy rows stored with hashed account
ids; update getSwapsByAccountId to include a legacy lookup path before removing
migration support by extending the predicate passed to paginateSwaps to OR also
match legacy columns (e.g., sellAccountIdHash and buyAccountIdHash) using the
same hash of the incoming accountId, and additionally include the pre-migration
receiveAddress match for buy-side lookups (i.e., OR { buyAccountId: accountId }
or { receiveAddress: accountId } and OR { sellAccountIdHash: hash(accountId) } /
{ buyAccountIdHash: hash(accountId) } ), using the existing paginateSwaps helper
and the same hashing utility used elsewhere so historical swaps continue to be
returned during migration.

In `@packages/shared-types/src/index.ts`:
- Around line 77-82: CreateSwapDto (and any DTO/type in this file that declares
affiliateBps, shapeshiftBps, isStreaming, metadata, partnerAddress, partnerBps)
currently marks partnerBps as optional; change partnerBps to be required (remove
the optional marker) so callers must pass it at quote time. Update the type
declaration for partnerBps in the CreateSwapDto/interface in this file and then
search for any call sites or tests that construct CreateSwapDto to ensure they
supply partnerBps (adjust mocks/fixtures accordingly) to prevent compile errors.

In `@prisma/migrations/20260527000000_swap_bps_and_partner_fields/migration.sql`:
- Around line 8-16: Add a DB-level invariant to ensure affiliateBps =
shapeshiftBps + partnerBps on the "swaps" table: after backfilling partnerBps
(using the existing UPDATE logic) run a verification SELECT to detect any rows
where "affiliateBps" <> ("shapeshiftBps" + "partnerBps") and fix them (or fail
the migration), then add an ALTER TABLE "swaps" ADD CONSTRAINT ... CHECK
("affiliateBps" = "shapeshiftBps" + "partnerBps") so future writes cannot
violate the three-way split; reference the columns partnerBps, affiliateBps,
shapeshiftBps and the "swaps" table when implementing.

---

Outside diff comments:
In `@apps/swap-service/src/verification/swap-verification.service.ts`:
- Around line 320-327: The returned object is including affiliate fields even
when hasShapeshiftAffiliate is false; update the return in the verification
result so that swap.affiliateBps, affiliateAddress (expectedTreasuryAddress) and
actualAffiliateFeeAmountCryptoBaseUnit (orderData?.context?.feeAmount) are only
set when hasShapeshiftAffiliate is true—otherwise omit or set them to undefined
and ensure hasAffiliate reflects hasShapeshiftAffiliate; locate the return
object in the function that computes hasShapeshiftAffiliate and adjust those
properties conditionally.
🪄 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: dbc51ef3-3a63-4a6e-b2df-9417d60b2f07

📥 Commits

Reviewing files that changed from the base of the PR and between 75bf563 and e582ed0.

📒 Files selected for processing (14)
  • apps/swap-service/src/affiliate/affiliate.controller.ts
  • apps/swap-service/src/affiliate/affiliate.service.ts
  • apps/swap-service/src/swaps/constants.ts
  • apps/swap-service/src/swaps/swaps.controller.ts
  • apps/swap-service/src/swaps/swaps.service.ts
  • apps/swap-service/src/swaps/utils.ts
  • apps/swap-service/src/verification/__tests__/fixtures/maya/swap.ts
  • apps/swap-service/src/verification/__tests__/fixtures/near/swap.ts
  • apps/swap-service/src/verification/__tests__/fixtures/relay/swap.ts
  • apps/swap-service/src/verification/__tests__/fixtures/thorchain/swap.ts
  • apps/swap-service/src/verification/swap-verification.service.ts
  • packages/shared-types/src/index.ts
  • prisma/migrations/20260527000000_swap_bps_and_partner_fields/migration.sql
  • prisma/schema/swap-service.prisma

Comment thread apps/swap-service/src/affiliate/affiliate.service.ts
Comment thread apps/swap-service/src/swaps/swaps.service.ts
Comment thread packages/shared-types/src/index.ts
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