From d504c2bb74e0dfe1c54afac255a7ce81a5ccbe19 Mon Sep 17 00:00:00 2001 From: kaladinlight <35275952+kaladinlight@users.noreply.github.com> Date: Wed, 27 May 2026 11:46:47 -0600 Subject: [PATCH 1/5] =?UTF-8?q?feat(swap-service):=20explicit=20bps=20spli?= =?UTF-8?q?t=20and=20affiliateAddress=20=E2=86=92=20partnerAddress=20renam?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../src/affiliate/affiliate.controller.ts | 12 +++---- .../src/affiliate/affiliate.service.ts | 19 +++++------ apps/swap-service/src/swaps/constants.ts | 2 ++ .../src/swaps/swaps.controller.ts | 6 ++-- apps/swap-service/src/swaps/swaps.service.ts | 33 +++++++++---------- apps/swap-service/src/swaps/utils.ts | 3 -- .../__tests__/fixtures/maya/swap.ts | 3 +- .../__tests__/fixtures/near/swap.ts | 3 +- .../__tests__/fixtures/relay/swap.ts | 5 +-- .../__tests__/fixtures/thorchain/swap.ts | 3 +- .../verification/swap-verification.service.ts | 2 +- packages/shared-types/src/index.ts | 10 +++--- .../migration.sql | 29 ++++++++++++++++ prisma/schema/swap-service.prisma | 11 ++++--- 14 files changed, 84 insertions(+), 57 deletions(-) create mode 100644 apps/swap-service/src/swaps/constants.ts create mode 100644 prisma/migrations/20260527000000_swap_bps_and_partner_fields/migration.sql diff --git a/apps/swap-service/src/affiliate/affiliate.controller.ts b/apps/swap-service/src/affiliate/affiliate.controller.ts index b31c5df..5caac30 100644 --- a/apps/swap-service/src/affiliate/affiliate.controller.ts +++ b/apps/swap-service/src/affiliate/affiliate.controller.ts @@ -13,10 +13,11 @@ import { UseGuards, } from '@nestjs/common' +import { SHAPESHIFT_BPS } from '../swaps/constants' + import { AffiliateService } from './affiliate.service' import { SiweAuthGuard, SiweRequest } from './siwe-auth.guard' import { - AddressQueryDto, AffiliateStatsQueryDto, AffiliateSwapsQueryDto, ClaimPartnerCodeDto, @@ -39,17 +40,12 @@ export class AffiliateController { return this.affiliateService.getAffiliateStats(query.address, query) } - @Get('lookup/bps') - async lookupBps(@Query() query: AddressQueryDto) { - const bps = await this.affiliateService.lookupAffiliateBps(query.address) - return { bps } - } - @Get(':address') async getAffiliate(@Param('address') address: string) { const affiliate = await this.affiliateService.getAffiliateByWalletAddress(address) if (!affiliate) throw new NotFoundException('Affiliate not found') - return affiliate + const { bps: partnerBps, ...rest } = affiliate + return { ...rest, partnerBps, shapeshiftBps: SHAPESHIFT_BPS } } @UseGuards(SiweAuthGuard) diff --git a/apps/swap-service/src/affiliate/affiliate.service.ts b/apps/swap-service/src/affiliate/affiliate.service.ts index f0baf20..bdc5db1 100644 --- a/apps/swap-service/src/affiliate/affiliate.service.ts +++ b/apps/swap-service/src/affiliate/affiliate.service.ts @@ -2,6 +2,7 @@ import { Injectable, NotFoundException } from '@nestjs/common' import { Affiliate, Prisma } from '@prisma/client' import { PrismaService } from '../prisma/prisma.service' +import { SHAPESHIFT_BPS } from '../swaps/constants' import { PaginatedSwaps } from '../swaps/types' import { calculateFeeForSwap, getAffiliateFeeRate, toSwap } from '../swaps/utils' import { getNextCursor, swapCursorArgs } from '../utils/pagination' @@ -76,12 +77,12 @@ export class AffiliateService { }) } - async getAffiliateStats(affiliateAddress: string, options: AffiliateStatsQueryDto): Promise { + async getAffiliateStats(address: string, options: AffiliateStatsQueryDto): Promise { const { startDate, endDate } = options const items = await this.prisma.swap.findMany({ where: { - affiliateAddress, + partnerAddress: address, status: 'SUCCESS', isAffiliateVerified: true, ...(startDate || endDate @@ -119,13 +120,13 @@ export class AffiliateService { } } - async getAffiliateSwaps(affiliateAddress: string, options: AffiliateSwapsQueryDto): Promise { + async getAffiliateSwaps(address: string, options: AffiliateSwapsQueryDto): Promise { const { startDate, endDate, limit, cursor } = options const items = await this.prisma.swap.findMany({ ...swapCursorArgs(limit, cursor), where: { - affiliateAddress, + partnerAddress: address, ...(startDate || endDate ? { createdAt: { @@ -148,14 +149,10 @@ export class AffiliateService { if (!affiliate) return null return { + partnerBps: affiliate.bps, partnerCode: affiliate.partnerCode, - affiliateAddress: affiliate.receiveAddress ?? affiliate.walletAddress, - bps: affiliate.bps, + partnerAddress: affiliate.receiveAddress ?? affiliate.walletAddress, + shapeshiftBps: SHAPESHIFT_BPS, } } - - async lookupAffiliateBps(affiliateAddress: string): Promise { - const affiliate = await this.getAffiliateByWalletAddress(affiliateAddress) - return affiliate?.bps ?? 60 - } } diff --git a/apps/swap-service/src/swaps/constants.ts b/apps/swap-service/src/swaps/constants.ts new file mode 100644 index 0000000..c81ced5 --- /dev/null +++ b/apps/swap-service/src/swaps/constants.ts @@ -0,0 +1,2 @@ +export const SHAPESHIFT_BPS = 10 +export const REFERRER_FEE_RATE = 0.1 diff --git a/apps/swap-service/src/swaps/swaps.controller.ts b/apps/swap-service/src/swaps/swaps.controller.ts index ab67b4a..bfb84d1 100644 --- a/apps/swap-service/src/swaps/swaps.controller.ts +++ b/apps/swap-service/src/swaps/swaps.controller.ts @@ -42,12 +42,12 @@ export class SwapsController { return this.swapsService.calculateReferralFees(referralCode, startDate, endDate) } - @Get('affiliate-fees/:affiliateAddress') + @Get('affiliate-fees/:address') async getAffiliateFees( - @Param('affiliateAddress') affiliateAddress: string, + @Param('address') address: string, @Query('startDate', OptionalDatePipe) startDate?: Date, @Query('endDate', OptionalDatePipe) endDate?: Date, ) { - return this.swapsService.calculateAffiliateFees(affiliateAddress, startDate, endDate) + return this.swapsService.calculateAffiliateFees(address, startDate, endDate) } } diff --git a/apps/swap-service/src/swaps/swaps.service.ts b/apps/swap-service/src/swaps/swaps.service.ts index 892553f..be50571 100644 --- a/apps/swap-service/src/swaps/swaps.service.ts +++ b/apps/swap-service/src/swaps/swaps.service.ts @@ -26,6 +26,7 @@ import { resolveAffiliateFeeAssetId } from '../utils/affiliateFeeAsset' import { getNextCursor, swapCursorArgs } from '../utils/pagination' import { SwapVerificationService } from '../verification/swap-verification.service' +import { REFERRER_FEE_RATE } from './constants' import { buildChainAdapterAsserts, getSwapperConfig } from './swapper-config' import type { AffiliateVerificationDetails, AggregateFeesParams, FeeTotals, PaginatedSwaps, Swap } from './types' import { PaginationQueryDto } from './types' @@ -47,9 +48,6 @@ export class SwapsService { private readonly userServiceClient: UserServiceClient private readonly chainAdapterAsserts: ReturnType - private static readonly API_BASE_BPS = 10 - private static readonly REFERRER_FEE_RATE = 0.1 - constructor( private prisma: PrismaService, private swapVerificationService: SwapVerificationService, @@ -82,10 +80,10 @@ export class SwapsService { try { const affiliateFeeAssetId = resolveAffiliateFeeAssetId(data.swapperName, data.sellAsset, data.buyAsset) - const [referralCode, prices, affiliateAddress] = await Promise.all([ + const [referralCode, prices, partnerAddress] = await Promise.all([ this.getReferralCode(data.userId), fetchUsdPrices(data, affiliateFeeAssetId), - this.resolveAffiliateAddress(data), + this.resolvePartnerAddress(data), ]) const sellAmountUsd = computeSellAmountUsd( @@ -117,10 +115,11 @@ export class SwapsService { sellAssetUsd: prices.sellAssetUsd, buyAssetUsd: prices.buyAssetUsd, affiliateAssetUsd: prices.affiliateAssetUsd, - affiliateAddress, - affiliateBps: data.affiliateBps ?? null, + partnerAddress, + partnerBps: data.partnerBps, + affiliateBps: data.affiliateBps, + shapeshiftBps: data.shapeshiftBps, origin: data.origin ?? null, - shapeshiftBps: SwapsService.API_BASE_BPS, affiliateFeeAssetId, }, }), @@ -130,7 +129,7 @@ export class SwapsService { [ `Swap created: ${swap.swapId}`, referralCode && `referral ${referralCode}`, - affiliateAddress && `affiliate ${affiliateAddress}`, + partnerAddress && `partner ${partnerAddress}`, sellAmountUsd && `$${sellAmountUsd}`, ] .filter(Boolean) @@ -149,8 +148,8 @@ export class SwapsService { return this.userServiceClient.getUserReferralCode(userId) } - private async resolveAffiliateAddress(data: CreateSwapDto): Promise { - if (data.affiliateAddress) return data.affiliateAddress + private async resolvePartnerAddress(data: CreateSwapDto): Promise { + if (data.partnerAddress) return data.partnerAddress if (!data.partnerCode) return null try { @@ -161,7 +160,7 @@ export class SwapsService { return affiliate?.receiveAddress ?? affiliate?.walletAddress ?? null } catch (error) { - logger.warn(`Failed to resolve affiliate address for partner code ${data.partnerCode}:`, error) + logger.warn(`Failed to resolve partner address for partner code ${data.partnerCode}:`, error) return null } } @@ -267,7 +266,7 @@ export class SwapsService { calcFee: (swap) => { const fee = calculateFeeForSwap(swap) if (!fee) return null - return { feeUsd: fee.feeUsd * SwapsService.REFERRER_FEE_RATE, volumeUsd: fee.volumeUsd } + return { feeUsd: fee.feeUsd * REFERRER_FEE_RATE, volumeUsd: fee.volumeUsd } }, }) @@ -287,13 +286,13 @@ export class SwapsService { } } - async calculateAffiliateFees(affiliateAddress: string, startDate?: Date, endDate?: Date): Promise { + async calculateAffiliateFees(address: string, startDate?: Date, endDate?: Date): Promise { logger.log( - `Calculating affiliate fees for address: ${affiliateAddress}, period: ${startDate?.toISOString()} - ${endDate?.toISOString()}`, + `Calculating affiliate fees for address: ${address}, period: ${startDate?.toISOString()} - ${endDate?.toISOString()}`, ) const fees = await this.aggregateFees({ - baseWhere: { affiliateAddress, isAffiliateVerified: true, status: 'SUCCESS', origin: 'api' }, + baseWhere: { partnerAddress: address, isAffiliateVerified: true, status: 'SUCCESS', origin: 'api' }, startDate, endDate, calcFee: (swap) => { @@ -305,7 +304,7 @@ export class SwapsService { }) logger.log( - `Affiliate fees for ${affiliateAddress}\n` + + `Affiliate fees for ${address}\n` + ` period: ${fees.periodCount} swaps, $${fees.periodVolumeUsd.toFixed(2)} volume, $${fees.periodFeesUsd.toFixed(2)} fee\n` + ` all-time: ${fees.allTimeCount} swaps, $${fees.allTimeFeesUsd.toFixed(2)} fee`, ) diff --git a/apps/swap-service/src/swaps/utils.ts b/apps/swap-service/src/swaps/utils.ts index 74ec722..42487f9 100644 --- a/apps/swap-service/src/swaps/utils.ts +++ b/apps/swap-service/src/swaps/utils.ts @@ -51,9 +51,6 @@ export const formatAmount = (amount: string | number): string => { } export const getAffiliateFeeRate = (verifiedBps: number, shapeshiftBps: number): number => { - // Partner configured at or below the platform floor — partner keeps the whole fee. - if (verifiedBps <= shapeshiftBps) return 1 - // Platform takes shapeshiftBps; partner gets the remainder. return (verifiedBps - shapeshiftBps) / verifiedBps } diff --git a/apps/swap-service/src/verification/__tests__/fixtures/maya/swap.ts b/apps/swap-service/src/verification/__tests__/fixtures/maya/swap.ts index 71e56f2..4de73a7 100644 --- a/apps/swap-service/src/verification/__tests__/fixtures/maya/swap.ts +++ b/apps/swap-service/src/verification/__tests__/fixtures/maya/swap.ts @@ -72,8 +72,9 @@ export default { affiliateAssetUsd: '2309.11', isAffiliateVerified: null, affiliateVerificationDetails: null, - affiliateAddress: '0xA44C286BA83Bb771cd0107B2c1Df678435Bd1535', + partnerAddress: '0xA44C286BA83Bb771cd0107B2c1Df678435Bd1535', affiliateBps: 60, + partnerBps: 50, origin: 'api', affiliateFeeAssetId: 'eip155:1/slip44:60', actualAffiliateFeeAmountCryptoBaseUnit: null, diff --git a/apps/swap-service/src/verification/__tests__/fixtures/near/swap.ts b/apps/swap-service/src/verification/__tests__/fixtures/near/swap.ts index f2fd508..5c2c060 100644 --- a/apps/swap-service/src/verification/__tests__/fixtures/near/swap.ts +++ b/apps/swap-service/src/verification/__tests__/fixtures/near/swap.ts @@ -72,8 +72,9 @@ export default { isAffiliateVerified: null, affiliateVerificationDetails: null, verificationStatus: 'PENDING', - affiliateAddress: '0xa44c286ba83bb771cd0107b2c1df678435bd1535', + partnerAddress: '0xa44c286ba83bb771cd0107b2c1df678435bd1535', affiliateBps: 60, + partnerBps: 50, origin: 'api', affiliateFeeAssetId: 'eip155:1/slip44:60', actualAffiliateFeeAmountCryptoBaseUnit: null, diff --git a/apps/swap-service/src/verification/__tests__/fixtures/relay/swap.ts b/apps/swap-service/src/verification/__tests__/fixtures/relay/swap.ts index 6c2edc4..ae9c0ea 100644 --- a/apps/swap-service/src/verification/__tests__/fixtures/relay/swap.ts +++ b/apps/swap-service/src/verification/__tests__/fixtures/relay/swap.ts @@ -81,10 +81,11 @@ export default { affiliateAddress: '0x9c9aA90363630d4ab1D9dbF416cc3BBC8d3Ed502', verifiedSellAmountCryptoBaseUnit: '1000000000000000', }, - affiliateAddress: null, + partnerAddress: null, affiliateBps: 60, + partnerBps: 0, origin: 'api', affiliateFeeAssetId: 'eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', actualAffiliateFeeAmountCryptoBaseUnit: '6000000000000', - shapeshiftBps: 10, + shapeshiftBps: 60, } satisfies Swap diff --git a/apps/swap-service/src/verification/__tests__/fixtures/thorchain/swap.ts b/apps/swap-service/src/verification/__tests__/fixtures/thorchain/swap.ts index 54fceb8..bd6fc38 100644 --- a/apps/swap-service/src/verification/__tests__/fixtures/thorchain/swap.ts +++ b/apps/swap-service/src/verification/__tests__/fixtures/thorchain/swap.ts @@ -72,8 +72,9 @@ export default { affiliateAssetUsd: '2315.29', isAffiliateVerified: null, affiliateVerificationDetails: null, - affiliateAddress: '0xA44C286BA83Bb771cd0107B2c1Df678435Bd1535', + partnerAddress: '0xA44C286BA83Bb771cd0107B2c1Df678435Bd1535', affiliateBps: 60, + partnerBps: 50, origin: 'api', affiliateFeeAssetId: 'eip155:1/slip44:60', actualAffiliateFeeAmountCryptoBaseUnit: null, diff --git a/apps/swap-service/src/verification/swap-verification.service.ts b/apps/swap-service/src/verification/swap-verification.service.ts index 1e0db47..00f070c 100644 --- a/apps/swap-service/src/verification/swap-verification.service.ts +++ b/apps/swap-service/src/verification/swap-verification.service.ts @@ -644,7 +644,7 @@ export class SwapVerificationService { // TODO: Implement on-chain/API verification for AVNU const affiliateBps = swap.affiliateBps ?? undefined const hasAffiliate = affiliateBps !== undefined && affiliateBps > 0 - const affiliateAddress = swap.affiliateAddress ?? undefined + const affiliateAddress = swap.partnerAddress ?? undefined const verifiedSellAmountCryptoBaseUnit = ( (metadata?.sellAmountIncludingProtocolFeesCryptoBaseUnit as string | undefined) ?? swap.sellAmountCryptoBaseUnit diff --git a/packages/shared-types/src/index.ts b/packages/shared-types/src/index.ts index 325eeea..320ef01 100644 --- a/packages/shared-types/src/index.ts +++ b/packages/shared-types/src/index.ts @@ -73,13 +73,15 @@ export interface CreateSwapDto { swapperName: SwapperName sellAccountId: string buyAccountId?: string - receiveAddress?: string + receiveAddress: string + affiliateBps: number + shapeshiftBps: number isStreaming?: boolean metadata?: Record - affiliateAddress?: string - affiliateBps?: number + partnerAddress?: string + partnerBps?: number partnerCode?: string - origin?: 'web' | 'api' | 'widget' + origin?: 'web' | 'api' } export interface UpdateSwapStatusDto { diff --git a/prisma/migrations/20260527000000_swap_bps_and_partner_fields/migration.sql b/prisma/migrations/20260527000000_swap_bps_and_partner_fields/migration.sql new file mode 100644 index 0000000..91714ab --- /dev/null +++ b/prisma/migrations/20260527000000_swap_bps_and_partner_fields/migration.sql @@ -0,0 +1,29 @@ +-- Backfill any NULL affiliateBps rows with the unattributed default (60) +UPDATE "swaps" SET "affiliateBps" = 60 WHERE "affiliateBps" IS NULL; + +-- Make affiliateBps non-nullable +ALTER TABLE "swaps" ALTER COLUMN "affiliateBps" SET NOT NULL; + +-- Add partnerBps column with default 0 +ALTER TABLE "swaps" ADD COLUMN "partnerBps" INTEGER NOT NULL DEFAULT 0; + +-- Backfill partnerBps for partner swaps (affiliateAddress present) +UPDATE "swaps" SET "partnerBps" = GREATEST(0, "affiliateBps" - "shapeshiftBps") + WHERE "affiliateAddress" IS NOT NULL; + +-- Fix shapeshiftBps for non-partner swaps (all fee goes to ShapeShift) +UPDATE "swaps" SET "shapeshiftBps" = "affiliateBps" + WHERE "affiliateAddress" IS NULL; + +-- Rename affiliateAddress -> partnerAddress +ALTER TABLE "swaps" RENAME COLUMN "affiliateAddress" TO "partnerAddress"; + +-- Drop old index and create new one with renamed column +DROP INDEX IF EXISTS "swaps_affiliateAddress_idx"; +CREATE INDEX "swaps_partnerAddress_idx" ON "swaps"("partnerAddress"); + +-- Backfill any NULL receiveAddress rows +UPDATE "swaps" SET "receiveAddress" = 'unknown' WHERE "receiveAddress" IS NULL; + +-- Make receiveAddress non-nullable +ALTER TABLE "swaps" ALTER COLUMN "receiveAddress" SET NOT NULL; diff --git a/prisma/schema/swap-service.prisma b/prisma/schema/swap-service.prisma index b6f655d..f6803ec 100644 --- a/prisma/schema/swap-service.prisma +++ b/prisma/schema/swap-service.prisma @@ -25,7 +25,7 @@ model Swap { swapperName String sellAccountId String buyAccountId String? - receiveAddress String? + receiveAddress String sellTxHash String? buyTxHash String? txLink String? @@ -41,16 +41,17 @@ model Swap { affiliateAssetUsd String? isAffiliateVerified Boolean? affiliateVerificationDetails Json? - affiliateAddress String? @db.Citext - affiliateBps Int? + partnerAddress String? @db.Citext + partnerBps Int @default(0) + affiliateBps Int + shapeshiftBps Int origin String? affiliateFeeAssetId String? actualAffiliateFeeAmountCryptoBaseUnit String? - shapeshiftBps Int verificationStatus VerificationStatus @default(PENDING) @@index([referralCode]) - @@index([affiliateAddress]) + @@index([partnerAddress]) @@index([status, sellTxHash]) @@index([verificationStatus, status]) @@index([userId]) From acad0273ee8e88244caab5472af18362ba469eeb Mon Sep 17 00:00:00 2001 From: kaladinlight <35275952+kaladinlight@users.noreply.github.com> Date: Wed, 27 May 2026 18:16:25 -0600 Subject: [PATCH 2/5] feat(swap-service): require buyAccountId, remove account hashing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- apps/swap-service/src/swaps/swaps.service.ts | 10 ++++------ .../src/verification/__tests__/fixtures/maya/swap.ts | 2 +- .../src/verification/__tests__/fixtures/near/swap.ts | 2 +- .../src/verification/__tests__/fixtures/relay/swap.ts | 2 +- .../verification/__tests__/fixtures/thorchain/swap.ts | 2 +- packages/shared-types/src/index.ts | 2 +- .../migration.sql | 6 ++++++ prisma/schema/swap-service.prisma | 2 +- 8 files changed, 16 insertions(+), 12 deletions(-) diff --git a/apps/swap-service/src/swaps/swaps.service.ts b/apps/swap-service/src/swaps/swaps.service.ts index be50571..9c5c283 100644 --- a/apps/swap-service/src/swaps/swaps.service.ts +++ b/apps/swap-service/src/swaps/swaps.service.ts @@ -8,7 +8,7 @@ import { import { Prisma } from '@prisma/client' import { CreateSwapDto, Fees, SwapStatusResponse, UpdateSwapStatusDto } from '@shapeshift/shared-types' -import { hashAccountId, NotificationsServiceClient, UserServiceClient } from '@shapeshift/shared-utils' +import { NotificationsServiceClient, UserServiceClient } from '@shapeshift/shared-utils' import { swappers } from '@shapeshiftoss/swapper' import { TxStatus } from '@shapeshiftoss/unchained-client' @@ -105,8 +105,8 @@ export class SwapsService { expectedBuyAmountCryptoBaseUnit: data.expectedBuyAmountCryptoBaseUnit, source: data.source, swapperName: data.swapperName, - sellAccountId: data.sellAccountId ? hashAccountId(data.sellAccountId) : 'api', - buyAccountId: data.buyAccountId ? hashAccountId(data.buyAccountId) : null, + sellAccountId: data.sellAccountId, + buyAccountId: data.buyAccountId, receiveAddress: data.receiveAddress, isStreaming: data.isStreaming ?? false, metadata: (data.metadata ?? {}) as Prisma.InputJsonValue, @@ -218,9 +218,7 @@ export class SwapsService { } async getSwapsByAccountId(accountId: string, options: PaginationQueryDto): Promise { - const hashedAccountId = hashAccountId(accountId) - - return this.paginateSwaps({ OR: [{ sellAccountId: hashedAccountId }, { buyAccountId: hashedAccountId }] }, options) + return this.paginateSwaps({ OR: [{ sellAccountId: accountId }, { buyAccountId: accountId }] }, options) } private async paginateSwaps(where: Prisma.SwapWhereInput, options: PaginationQueryDto): Promise { diff --git a/apps/swap-service/src/verification/__tests__/fixtures/maya/swap.ts b/apps/swap-service/src/verification/__tests__/fixtures/maya/swap.ts index 4de73a7..b4647a3 100644 --- a/apps/swap-service/src/verification/__tests__/fixtures/maya/swap.ts +++ b/apps/swap-service/src/verification/__tests__/fixtures/maya/swap.ts @@ -45,7 +45,7 @@ export default { source: 'MAYAChain', swapperName: SwapperName.Mayachain, sellAccountId: '3c70e97c6f86a5b5cfdf82dfd3380ac4ed3d8b89dabf86f631bdf739372926be', - buyAccountId: null, + buyAccountId: '3c70e97c6f86a5b5cfdf82dfd3380ac4ed3d8b89dabf86f631bdf739372926be', receiveAddress: '0xA44C286BA83Bb771cd0107B2c1Df678435Bd1535', sellTxHash: '0xe4dac04d7194aba8f3a9fa154cd7be3055a03d35797f1916d8721f6273fa16eb', buyTxHash: '0x389BD007E29E081811C735F773D360A05DFC3E0C8608781A85602A7C7AE2AD4B', diff --git a/apps/swap-service/src/verification/__tests__/fixtures/near/swap.ts b/apps/swap-service/src/verification/__tests__/fixtures/near/swap.ts index 5c2c060..8bf9764 100644 --- a/apps/swap-service/src/verification/__tests__/fixtures/near/swap.ts +++ b/apps/swap-service/src/verification/__tests__/fixtures/near/swap.ts @@ -39,7 +39,7 @@ export default { source: 'NEAR Intents', swapperName: SwapperName.NearIntents, sellAccountId: '3c70e97c6f86a5b5cfdf82dfd3380ac4ed3d8b89dabf86f631bdf739372926be', - buyAccountId: null, + buyAccountId: '3c70e97c6f86a5b5cfdf82dfd3380ac4ed3d8b89dabf86f631bdf739372926be', receiveAddress: '0xA44C286BA83Bb771cd0107B2c1Df678435Bd1535', sellTxHash: '0x134662bb1608962d8c4c6cf4b32bc7a10fefa282ced805b90c04be809ee93310', buyTxHash: null, diff --git a/apps/swap-service/src/verification/__tests__/fixtures/relay/swap.ts b/apps/swap-service/src/verification/__tests__/fixtures/relay/swap.ts index ae9c0ea..af35376 100644 --- a/apps/swap-service/src/verification/__tests__/fixtures/relay/swap.ts +++ b/apps/swap-service/src/verification/__tests__/fixtures/relay/swap.ts @@ -39,7 +39,7 @@ export default { source: 'Relay', swapperName: SwapperName.Relay, sellAccountId: '3c70e97c6f86a5b5cfdf82dfd3380ac4ed3d8b89dabf86f631bdf739372926be', - buyAccountId: null, + buyAccountId: '3c70e97c6f86a5b5cfdf82dfd3380ac4ed3d8b89dabf86f631bdf739372926be', receiveAddress: '0xA44C286BA83Bb771cd0107B2c1Df678435Bd1535', sellTxHash: '0x77395ef0ae19f5fc083d305dec06f1fbc87afe34686b33a7648a56ca687315df', buyTxHash: '0x77395ef0ae19f5fc083d305dec06f1fbc87afe34686b33a7648a56ca687315df', diff --git a/apps/swap-service/src/verification/__tests__/fixtures/thorchain/swap.ts b/apps/swap-service/src/verification/__tests__/fixtures/thorchain/swap.ts index bd6fc38..a419c8e 100644 --- a/apps/swap-service/src/verification/__tests__/fixtures/thorchain/swap.ts +++ b/apps/swap-service/src/verification/__tests__/fixtures/thorchain/swap.ts @@ -45,7 +45,7 @@ export default { source: 'THORChain', swapperName: SwapperName.Thorchain, sellAccountId: '3c70e97c6f86a5b5cfdf82dfd3380ac4ed3d8b89dabf86f631bdf739372926be', - buyAccountId: null, + buyAccountId: '3c70e97c6f86a5b5cfdf82dfd3380ac4ed3d8b89dabf86f631bdf739372926be', receiveAddress: '0xA44C286BA83Bb771cd0107B2c1Df678435Bd1535', sellTxHash: '0x4a4ce957a047378c3f3ac57ca3cc3ab03e61814a7b415f493704d84a9e42d0eb', buyTxHash: '0xE6CECF7727DA23DDC52605D728775E7E022935987548FF5E9E922FDFBC55F9FC', diff --git a/packages/shared-types/src/index.ts b/packages/shared-types/src/index.ts index 320ef01..301bb8e 100644 --- a/packages/shared-types/src/index.ts +++ b/packages/shared-types/src/index.ts @@ -72,7 +72,7 @@ export interface CreateSwapDto { source: string swapperName: SwapperName sellAccountId: string - buyAccountId?: string + buyAccountId: string receiveAddress: string affiliateBps: number shapeshiftBps: number diff --git a/prisma/migrations/20260527000000_swap_bps_and_partner_fields/migration.sql b/prisma/migrations/20260527000000_swap_bps_and_partner_fields/migration.sql index 91714ab..3c633e6 100644 --- a/prisma/migrations/20260527000000_swap_bps_and_partner_fields/migration.sql +++ b/prisma/migrations/20260527000000_swap_bps_and_partner_fields/migration.sql @@ -27,3 +27,9 @@ UPDATE "swaps" SET "receiveAddress" = 'unknown' WHERE "receiveAddress" IS NULL; -- Make receiveAddress non-nullable ALTER TABLE "swaps" ALTER COLUMN "receiveAddress" SET NOT NULL; + +-- Backfill NULL buyAccountId with receiveAddress (destination = buy account) +UPDATE "swaps" SET "buyAccountId" = "receiveAddress" WHERE "buyAccountId" IS NULL; + +-- Make buyAccountId non-nullable +ALTER TABLE "swaps" ALTER COLUMN "buyAccountId" SET NOT NULL; diff --git a/prisma/schema/swap-service.prisma b/prisma/schema/swap-service.prisma index f6803ec..8e666b9 100644 --- a/prisma/schema/swap-service.prisma +++ b/prisma/schema/swap-service.prisma @@ -24,7 +24,7 @@ model Swap { source String swapperName String sellAccountId String - buyAccountId String? + buyAccountId String receiveAddress String sellTxHash String? buyTxHash String? From e582ed0e9c0e208c3a1af395048b479b868d7d85 Mon Sep 17 00:00:00 2001 From: kaladinlight <35275952+kaladinlight@users.noreply.github.com> Date: Wed, 27 May 2026 18:38:19 -0600 Subject: [PATCH 3/5] fix(swap-service): partner-first fee math and verifier cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../src/affiliate/affiliate.service.ts | 4 ++-- apps/swap-service/src/swaps/swaps.service.ts | 4 ++-- apps/swap-service/src/swaps/utils.ts | 5 ++-- .../__tests__/fixtures/maya/swap.ts | 4 ++-- .../__tests__/fixtures/near/swap.ts | 4 ++-- .../__tests__/fixtures/relay/swap.ts | 4 ++-- .../__tests__/fixtures/thorchain/swap.ts | 4 ++-- .../verification/swap-verification.service.ts | 24 +++++++++---------- 8 files changed, 27 insertions(+), 26 deletions(-) diff --git a/apps/swap-service/src/affiliate/affiliate.service.ts b/apps/swap-service/src/affiliate/affiliate.service.ts index bdc5db1..4307187 100644 --- a/apps/swap-service/src/affiliate/affiliate.service.ts +++ b/apps/swap-service/src/affiliate/affiliate.service.ts @@ -4,7 +4,7 @@ import { Affiliate, Prisma } from '@prisma/client' import { PrismaService } from '../prisma/prisma.service' import { SHAPESHIFT_BPS } from '../swaps/constants' import { PaginatedSwaps } from '../swaps/types' -import { calculateFeeForSwap, getAffiliateFeeRate, toSwap } from '../swaps/utils' +import { calculateFeeForSwap, getPartnerFeeRate, toSwap } from '../swaps/utils' import { getNextCursor, swapCursorArgs } from '../utils/pagination' import type { @@ -106,7 +106,7 @@ export class AffiliateService { const fee = calculateFeeForSwap(swap) if (!fee) continue - const rate = getAffiliateFeeRate(fee.verifiedBps, swap.shapeshiftBps) + const rate = getPartnerFeeRate(fee.verifiedBps, swap.partnerBps) totalSwaps++ totalVolumeUsd += fee.volumeUsd diff --git a/apps/swap-service/src/swaps/swaps.service.ts b/apps/swap-service/src/swaps/swaps.service.ts index 9c5c283..3979a44 100644 --- a/apps/swap-service/src/swaps/swaps.service.ts +++ b/apps/swap-service/src/swaps/swaps.service.ts @@ -35,7 +35,7 @@ import { calculateFeeForSwap, computeSellAmountUsd, fetchUsdPrices, - getAffiliateFeeRate, + getPartnerFeeRate, toSwap, toSwapperSwap, } from './utils' @@ -296,7 +296,7 @@ export class SwapsService { calcFee: (swap) => { const fee = calculateFeeForSwap(swap) if (!fee) return null - const rate = getAffiliateFeeRate(fee.verifiedBps, swap.shapeshiftBps) + const rate = getPartnerFeeRate(fee.verifiedBps, swap.partnerBps) return { feeUsd: fee.feeUsd * rate, volumeUsd: fee.volumeUsd } }, }) diff --git a/apps/swap-service/src/swaps/utils.ts b/apps/swap-service/src/swaps/utils.ts index 42487f9..65acc69 100644 --- a/apps/swap-service/src/swaps/utils.ts +++ b/apps/swap-service/src/swaps/utils.ts @@ -50,8 +50,9 @@ export const formatAmount = (amount: string | number): string => { .replace(/\.?0+$/, '') } -export const getAffiliateFeeRate = (verifiedBps: number, shapeshiftBps: number): number => { - return (verifiedBps - shapeshiftBps) / verifiedBps +export const getPartnerFeeRate = (verifiedBps: number, partnerBps: number): number => { + if (verifiedBps <= 0) return 0 + return Math.min(partnerBps / verifiedBps, 1) } export const computeSellAmountUsd = ( diff --git a/apps/swap-service/src/verification/__tests__/fixtures/maya/swap.ts b/apps/swap-service/src/verification/__tests__/fixtures/maya/swap.ts index b4647a3..e490094 100644 --- a/apps/swap-service/src/verification/__tests__/fixtures/maya/swap.ts +++ b/apps/swap-service/src/verification/__tests__/fixtures/maya/swap.ts @@ -44,8 +44,8 @@ export default { status: 'SUCCESS', source: 'MAYAChain', swapperName: SwapperName.Mayachain, - sellAccountId: '3c70e97c6f86a5b5cfdf82dfd3380ac4ed3d8b89dabf86f631bdf739372926be', - buyAccountId: '3c70e97c6f86a5b5cfdf82dfd3380ac4ed3d8b89dabf86f631bdf739372926be', + sellAccountId: '0xA44C286BA83Bb771cd0107B2c1Df678435Bd1535', + buyAccountId: '0xA44C286BA83Bb771cd0107B2c1Df678435Bd1535', receiveAddress: '0xA44C286BA83Bb771cd0107B2c1Df678435Bd1535', sellTxHash: '0xe4dac04d7194aba8f3a9fa154cd7be3055a03d35797f1916d8721f6273fa16eb', buyTxHash: '0x389BD007E29E081811C735F773D360A05DFC3E0C8608781A85602A7C7AE2AD4B', diff --git a/apps/swap-service/src/verification/__tests__/fixtures/near/swap.ts b/apps/swap-service/src/verification/__tests__/fixtures/near/swap.ts index 8bf9764..6271015 100644 --- a/apps/swap-service/src/verification/__tests__/fixtures/near/swap.ts +++ b/apps/swap-service/src/verification/__tests__/fixtures/near/swap.ts @@ -38,8 +38,8 @@ export default { status: 'PENDING', source: 'NEAR Intents', swapperName: SwapperName.NearIntents, - sellAccountId: '3c70e97c6f86a5b5cfdf82dfd3380ac4ed3d8b89dabf86f631bdf739372926be', - buyAccountId: '3c70e97c6f86a5b5cfdf82dfd3380ac4ed3d8b89dabf86f631bdf739372926be', + sellAccountId: '0xA44C286BA83Bb771cd0107B2c1Df678435Bd1535', + buyAccountId: '0xA44C286BA83Bb771cd0107B2c1Df678435Bd1535', receiveAddress: '0xA44C286BA83Bb771cd0107B2c1Df678435Bd1535', sellTxHash: '0x134662bb1608962d8c4c6cf4b32bc7a10fefa282ced805b90c04be809ee93310', buyTxHash: null, diff --git a/apps/swap-service/src/verification/__tests__/fixtures/relay/swap.ts b/apps/swap-service/src/verification/__tests__/fixtures/relay/swap.ts index af35376..dcd81b9 100644 --- a/apps/swap-service/src/verification/__tests__/fixtures/relay/swap.ts +++ b/apps/swap-service/src/verification/__tests__/fixtures/relay/swap.ts @@ -38,8 +38,8 @@ export default { status: 'SUCCESS', source: 'Relay', swapperName: SwapperName.Relay, - sellAccountId: '3c70e97c6f86a5b5cfdf82dfd3380ac4ed3d8b89dabf86f631bdf739372926be', - buyAccountId: '3c70e97c6f86a5b5cfdf82dfd3380ac4ed3d8b89dabf86f631bdf739372926be', + sellAccountId: '0xA44C286BA83Bb771cd0107B2c1Df678435Bd1535', + buyAccountId: '0xA44C286BA83Bb771cd0107B2c1Df678435Bd1535', receiveAddress: '0xA44C286BA83Bb771cd0107B2c1Df678435Bd1535', sellTxHash: '0x77395ef0ae19f5fc083d305dec06f1fbc87afe34686b33a7648a56ca687315df', buyTxHash: '0x77395ef0ae19f5fc083d305dec06f1fbc87afe34686b33a7648a56ca687315df', diff --git a/apps/swap-service/src/verification/__tests__/fixtures/thorchain/swap.ts b/apps/swap-service/src/verification/__tests__/fixtures/thorchain/swap.ts index a419c8e..c2a00f4 100644 --- a/apps/swap-service/src/verification/__tests__/fixtures/thorchain/swap.ts +++ b/apps/swap-service/src/verification/__tests__/fixtures/thorchain/swap.ts @@ -44,8 +44,8 @@ export default { status: 'SUCCESS', source: 'THORChain', swapperName: SwapperName.Thorchain, - sellAccountId: '3c70e97c6f86a5b5cfdf82dfd3380ac4ed3d8b89dabf86f631bdf739372926be', - buyAccountId: '3c70e97c6f86a5b5cfdf82dfd3380ac4ed3d8b89dabf86f631bdf739372926be', + sellAccountId: '0xA44C286BA83Bb771cd0107B2c1Df678435Bd1535', + buyAccountId: '0xA44C286BA83Bb771cd0107B2c1Df678435Bd1535', receiveAddress: '0xA44C286BA83Bb771cd0107B2c1Df678435Bd1535', sellTxHash: '0x4a4ce957a047378c3f3ac57ca3cc3ab03e61814a7b415f493704d84a9e42d0eb', buyTxHash: '0xE6CECF7727DA23DDC52605D728775E7E022935987548FF5E9E922FDFBC55F9FC', diff --git a/apps/swap-service/src/verification/swap-verification.service.ts b/apps/swap-service/src/verification/swap-verification.service.ts index 00f070c..108cb0d 100644 --- a/apps/swap-service/src/verification/swap-verification.service.ts +++ b/apps/swap-service/src/verification/swap-verification.service.ts @@ -320,7 +320,7 @@ export class SwapVerificationService { return { verificationStatus: 'SUCCESS', hasAffiliate: hasShapeshiftAffiliate, - affiliateBps: swap.affiliateBps ?? undefined, + affiliateBps: swap.affiliateBps, affiliateAddress: hasShapeshiftAffiliate ? expectedTreasuryAddress : undefined, verifiedSellAmountCryptoBaseUnit, actualBuyAmountCryptoBaseUnit: undefined, @@ -567,7 +567,7 @@ export class SwapVerificationService { const entrance = bridgeInfo.entrance const hasShapeshiftAffiliate = entrance?.toLowerCase() === this.shapeshiftButterswapEntrance.toLowerCase() - const affiliateBps = swap.affiliateBps ?? undefined + const affiliateBps = swap.affiliateBps const verifiedSellAmountCryptoBaseUnit = ( (metadata?.sellAmountIncludingProtocolFeesCryptoBaseUnit as string | undefined) ?? swap.sellAmountCryptoBaseUnit @@ -592,8 +592,8 @@ export class SwapVerificationService { if (!txHash) return noAffiliateResult('FAILED', 'Missing txHash for Cetus verification') // TODO: Implement on-chain/API verification for Cetus - const affiliateBps = swap.affiliateBps ?? undefined - const hasAffiliate = affiliateBps !== undefined && affiliateBps > 0 + const affiliateBps = swap.affiliateBps + const hasAffiliate = affiliateBps > 0 const verifiedSellAmountCryptoBaseUnit = ( (metadata?.sellAmountIncludingProtocolFeesCryptoBaseUnit as string | undefined) ?? swap.sellAmountCryptoBaseUnit @@ -617,8 +617,8 @@ export class SwapVerificationService { if (!txHash) return noAffiliateResult('FAILED', 'Missing txHash for Sun.io verification') // TODO: Implement on-chain/API verification for Sun.io - const affiliateBps = swap.affiliateBps ?? undefined - const hasAffiliate = affiliateBps !== undefined && affiliateBps > 0 + const affiliateBps = swap.affiliateBps + const hasAffiliate = affiliateBps > 0 const verifiedSellAmountCryptoBaseUnit = ( (metadata?.sellAmountIncludingProtocolFeesCryptoBaseUnit as string | undefined) ?? swap.sellAmountCryptoBaseUnit @@ -642,8 +642,8 @@ export class SwapVerificationService { if (!txHash) return noAffiliateResult('FAILED', 'Missing txHash for AVNU verification') // TODO: Implement on-chain/API verification for AVNU - const affiliateBps = swap.affiliateBps ?? undefined - const hasAffiliate = affiliateBps !== undefined && affiliateBps > 0 + const affiliateBps = swap.affiliateBps + const hasAffiliate = affiliateBps > 0 const affiliateAddress = swap.partnerAddress ?? undefined const verifiedSellAmountCryptoBaseUnit = ( @@ -673,9 +673,9 @@ export class SwapVerificationService { const referrerAddress = stonfiSpecific?.referrerAddress - const affiliateBps = swap.affiliateBps ?? stonfiSpecific?.referrerFeeBps ?? undefined + const affiliateBps = swap.affiliateBps - const hasAffiliate = !!referrerAddress && (affiliateBps !== undefined ? affiliateBps > 0 : false) + const hasAffiliate = !!referrerAddress && affiliateBps > 0 const verifiedSellAmountCryptoBaseUnit = ( (metadata?.sellAmountIncludingProtocolFeesCryptoBaseUnit as string | undefined) ?? swap.sellAmountCryptoBaseUnit @@ -708,8 +708,8 @@ export class SwapVerificationService { // eslint-disable-next-line @typescript-eslint/no-unused-vars const depositStatus = response.data - const affiliateBps = swap.affiliateBps ?? undefined - const hasAffiliate = affiliateBps !== undefined && affiliateBps > 0 + const affiliateBps = swap.affiliateBps + const hasAffiliate = affiliateBps > 0 const affiliateAddress = (metadata?.appFeeRecipient as string | undefined) || (metadata?.integratorId as string | undefined) From 20ff63e991bbba3a71b2589e135f839989e967df Mon Sep 17 00:00:00 2001 From: kaladinlight <35275952+kaladinlight@users.noreply.github.com> Date: Thu, 28 May 2026 12:00:51 -0600 Subject: [PATCH 4/5] chore(swap-service): format verifier fixture JSON Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/verification/__tests__/fixtures/maya/response.json | 7 ++----- .../__tests__/fixtures/thorchain/response.json | 5 +---- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/apps/swap-service/src/verification/__tests__/fixtures/maya/response.json b/apps/swap-service/src/verification/__tests__/fixtures/maya/response.json index d3a9482..3c72df9 100644 --- a/apps/swap-service/src/verification/__tests__/fixtures/maya/response.json +++ b/apps/swap-service/src/verification/__tests__/fixtures/maya/response.json @@ -78,10 +78,7 @@ "txID": "" } ], - "pools": [ - "ETH.ETH", - "ETH.USDC-0XA0B86991C6218B36C1D19D4A2E9EB0CE3606EB48" - ], + "pools": ["ETH.ETH", "ETH.USDC-0XA0B86991C6218B36C1D19D4A2E9EB0CE3606EB48"], "status": "success", "type": "swap" } @@ -91,4 +88,4 @@ "nextPageToken": "165627049000000001", "prevPageToken": "165627049000000001" } -} \ No newline at end of file +} diff --git a/apps/swap-service/src/verification/__tests__/fixtures/thorchain/response.json b/apps/swap-service/src/verification/__tests__/fixtures/thorchain/response.json index 27a3f43..50e7568 100644 --- a/apps/swap-service/src/verification/__tests__/fixtures/thorchain/response.json +++ b/apps/swap-service/src/verification/__tests__/fixtures/thorchain/response.json @@ -60,10 +60,7 @@ "txID": "E6CECF7727DA23DDC52605D728775E7E022935987548FF5E9E922FDFBC55F9FC" } ], - "pools": [ - "ETH.ETH", - "ETH.USDC-0XA0B86991C6218B36C1D19D4A2E9EB0CE3606EB48" - ], + "pools": ["ETH.ETH", "ETH.USDC-0XA0B86991C6218B36C1D19D4A2E9EB0CE3606EB48"], "status": "success", "type": "swap" } From 118782af3b147b381c0d8b1460a26ed3740ae15a Mon Sep 17 00:00:00 2001 From: kaladinlight <35275952+kaladinlight@users.noreply.github.com> Date: Thu, 28 May 2026 13:37:54 -0600 Subject: [PATCH 5/5] refactor(swap-service): extract toAffiliateResponse helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Centralizes the DB → response reshape (bps → partnerBps, add shapeshiftBps) and applies it consistently to all affiliate-returning endpoints (get, create, update, claim-code). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/affiliate/affiliate.controller.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/swap-service/src/affiliate/affiliate.controller.ts b/apps/swap-service/src/affiliate/affiliate.controller.ts index 5caac30..1513458 100644 --- a/apps/swap-service/src/affiliate/affiliate.controller.ts +++ b/apps/swap-service/src/affiliate/affiliate.controller.ts @@ -12,6 +12,7 @@ import { Req, UseGuards, } from '@nestjs/common' +import type { Affiliate } from '@prisma/client' import { SHAPESHIFT_BPS } from '../swaps/constants' @@ -26,6 +27,11 @@ import { } from './types' import { assertSiweMatches } from './utils' +const toAffiliateResponse = (affiliate: Affiliate) => { + const { bps: partnerBps, ...rest } = affiliate + return { ...rest, partnerBps, shapeshiftBps: SHAPESHIFT_BPS } +} + @Controller('v1/affiliate') export class AffiliateController { constructor(private affiliateService: AffiliateService) {} @@ -44,8 +50,7 @@ export class AffiliateController { async getAffiliate(@Param('address') address: string) { const affiliate = await this.affiliateService.getAffiliateByWalletAddress(address) if (!affiliate) throw new NotFoundException('Affiliate not found') - const { bps: partnerBps, ...rest } = affiliate - return { ...rest, partnerBps, shapeshiftBps: SHAPESHIFT_BPS } + return toAffiliateResponse(affiliate) } @UseGuards(SiweAuthGuard) @@ -54,7 +59,7 @@ export class AffiliateController { assertSiweMatches(req, data.walletAddress, 'Authenticated address does not match walletAddress') try { - return await this.affiliateService.createAffiliate(data) + return toAffiliateResponse(await this.affiliateService.createAffiliate(data)) } catch (error) { if (error instanceof Error) { if (error.message.includes('already')) throw new ConflictException(error.message) @@ -68,7 +73,7 @@ export class AffiliateController { async updateAffiliate(@Req() req: SiweRequest, @Param('address') address: string, @Body() data: UpdateAffiliateDto) { assertSiweMatches(req, address, 'Authenticated address does not match target address') - return this.affiliateService.updateAffiliate(address, data) + return toAffiliateResponse(await this.affiliateService.updateAffiliate(address, data)) } @UseGuards(SiweAuthGuard) @@ -77,7 +82,7 @@ export class AffiliateController { assertSiweMatches(req, data.walletAddress, 'Authenticated address does not match walletAddress') try { - return await this.affiliateService.claimPartnerCode(data.walletAddress, data.partnerCode) + return toAffiliateResponse(await this.affiliateService.claimPartnerCode(data.walletAddress, data.partnerCode)) } catch (error) { if (error instanceof Error) { if (error.message.includes('taken') || error.message.includes('reserved')) {