From 11b8914a1059bb83a157840ef413c7918a8dbdf0 Mon Sep 17 00:00:00 2001 From: NeOMakinG <14963751+NeOMakinG@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:08:26 +0100 Subject: [PATCH 1/6] feat(swap-service): add affiliate address tracking alongside referral codes - Add affiliateAddress field to Swap model (nullable, indexed) - Update CreateSwapDto to accept affiliateAddress - Calculate and store sellAmountUsd at swap creation time - Add calculateAffiliateFees method querying by affiliateAddress - Add GET /swaps/affiliate-fees/:affiliateAddress endpoint - Existing referralCode system unchanged --- .../migration.sql | 5 + apps/swap-service/prisma/schema.prisma | 2 + .../src/swaps/swaps.controller.ts | 26 +- apps/swap-service/src/swaps/swaps.service.ts | 398 +++++++++++++++--- packages/shared-types/src/index.ts | 6 +- 5 files changed, 369 insertions(+), 68 deletions(-) create mode 100644 apps/swap-service/prisma/migrations/20260223000000_add_affiliate_address/migration.sql diff --git a/apps/swap-service/prisma/migrations/20260223000000_add_affiliate_address/migration.sql b/apps/swap-service/prisma/migrations/20260223000000_add_affiliate_address/migration.sql new file mode 100644 index 0000000..edcebd2 --- /dev/null +++ b/apps/swap-service/prisma/migrations/20260223000000_add_affiliate_address/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "public"."swaps" ADD COLUMN "affiliateAddress" TEXT; + +-- CreateIndex +CREATE INDEX "swaps_affiliateAddress_idx" ON "public"."swaps"("affiliateAddress"); diff --git a/apps/swap-service/prisma/schema.prisma b/apps/swap-service/prisma/schema.prisma index 6be91b6..9aa2081 100644 --- a/apps/swap-service/prisma/schema.prisma +++ b/apps/swap-service/prisma/schema.prisma @@ -45,7 +45,9 @@ model Swap { isAffiliateVerified Boolean? affiliateVerificationDetails Json? affiliateVerifiedAt DateTime? + affiliateAddress String? @@index([referralCode]) + @@index([affiliateAddress]) @@map("swaps") } diff --git a/apps/swap-service/src/swaps/swaps.controller.ts b/apps/swap-service/src/swaps/swaps.controller.ts index 529fe44..8a9d444 100644 --- a/apps/swap-service/src/swaps/swaps.controller.ts +++ b/apps/swap-service/src/swaps/swaps.controller.ts @@ -2,12 +2,13 @@ import { Controller, Post, Get, Put, Param, Body, Query } from '@nestjs/common'; import { SwapsService } from './swaps.service'; import { SwapPollingService } from '../polling/swap-polling.service'; import { SwapVerificationService } from '../verification/swap-verification.service'; -export { - Swap, - Prisma -} from '@prisma/client'; +export { Swap, Prisma } from '@prisma/client'; import { Asset } from '@shapeshiftoss/types'; -import { CreateSwapDto, UpdateSwapStatusDto, VerifySwapAffiliateDto } from '@shapeshift/shared-types'; +import { + CreateSwapDto, + UpdateSwapStatusDto, + VerifySwapAffiliateDto, +} from '@shapeshift/shared-types'; @Controller('swaps') export class SwapsController { @@ -65,6 +66,21 @@ export class SwapsController { return this.swapsService.calculateReferralFees(referralCode, start, end); } + @Get('affiliate-fees/:affiliateAddress') + async getAffiliateFees( + @Param('affiliateAddress') affiliateAddress: string, + @Query('startDate') startDate?: string, + @Query('endDate') endDate?: string, + ) { + const start = startDate ? new Date(startDate) : undefined; + const end = endDate ? new Date(endDate) : undefined; + return this.swapsService.calculateAffiliateFees( + affiliateAddress, + start, + end, + ); + } + @Get(':swapId') async getSwap(@Param('swapId') swapId: string) { const swap = await this.swapsService['prisma'].swap.findUnique({ diff --git a/apps/swap-service/src/swaps/swaps.service.ts b/apps/swap-service/src/swaps/swaps.service.ts index 957c30e..e4ae8c5 100644 --- a/apps/swap-service/src/swaps/swaps.service.ts +++ b/apps/swap-service/src/swaps/swaps.service.ts @@ -6,12 +6,24 @@ import { UtxoChainAdapterService } from '../lib/chain-adapters/utxo.service'; import { CosmosSdkChainAdapterService } from '../lib/chain-adapters/cosmos-sdk.service'; import { SolanaChainAdapterService } from '../lib/chain-adapters/solana.service'; import { SwapVerificationService } from '../verification/swap-verification.service'; -import { SwapperName, swappers, SwapSource, SwapStatus } from '@shapeshiftoss/swapper'; +import { + SwapperName, + swappers, + SwapSource, + SwapStatus, +} from '@shapeshiftoss/swapper'; import { ChainId } from '@shapeshiftoss/caip'; import { Asset } from '@shapeshiftoss/types'; import { hashAccountId } from '@shapeshift/shared-utils'; -import { NotificationsServiceClient, UserServiceClient } from '@shapeshift/shared-utils'; -import { CreateSwapDto, SwapStatusResponse, UpdateSwapStatusDto } from '@shapeshift/shared-types'; +import { + NotificationsServiceClient, + UserServiceClient, +} from '@shapeshift/shared-utils'; +import { + CreateSwapDto, + SwapStatusResponse, + UpdateSwapStatusDto, +} from '@shapeshift/shared-types'; import { bnOrZero } from '@shapeshiftoss/chain-adapters'; @Injectable() @@ -34,20 +46,44 @@ export class SwapsService { async createSwap(data: CreateSwapDto) { try { - // Fetch referral code from user-service if userId is provided let referralCode: string | null = null; if (data.userId) { try { - referralCode = await this.userServiceClient.getUserReferralCode(data.userId); + referralCode = await this.userServiceClient.getUserReferralCode( + data.userId, + ); if (referralCode) { - this.logger.log(`Found referral code ${referralCode} for user ${data.userId}`); + this.logger.log( + `Found referral code ${referralCode} for user ${data.userId}`, + ); } } catch (error) { - this.logger.warn(`Failed to fetch referral code for user ${data.userId}:`, error); - // Continue swap creation even if referral code fetch fails + this.logger.warn( + `Failed to fetch referral code for user ${data.userId}:`, + error, + ); } } + let sellAmountUsd: string | null = null; + try { + const { getAssetPriceUsd, calculateUsdValue } = await import( + '../utils/pricing' + ); + const price = await getAssetPriceUsd(data.sellAsset); + if (price) { + sellAmountUsd = calculateUsdValue( + data.sellAmountCryptoPrecision, + price, + ); + } + } catch (error) { + this.logger.warn( + `Failed to calculate sellAmountUsd for swap ${data.swapId}:`, + error, + ); + } + const swap = await this.prisma.swap.create({ data: { swapId: data.swapId, @@ -57,20 +93,30 @@ export class SwapsService { sellAmountCryptoBaseUnit: data.sellAmountCryptoBaseUnit, expectedBuyAmountCryptoBaseUnit: data.expectedBuyAmountCryptoBaseUnit, sellAmountCryptoPrecision: data.sellAmountCryptoPrecision, - expectedBuyAmountCryptoPrecision: data.expectedBuyAmountCryptoPrecision, + expectedBuyAmountCryptoPrecision: + data.expectedBuyAmountCryptoPrecision, source: data.source, swapperName: data.swapperName, sellAccountId: hashAccountId(data.sellAccountId), - buyAccountId: data.buyAccountId ? hashAccountId(data.buyAccountId) : null, + buyAccountId: data.buyAccountId + ? hashAccountId(data.buyAccountId) + : null, receiveAddress: data.receiveAddress, isStreaming: data.isStreaming || false, metadata: data.metadata || {}, userId: data.userId, referralCode, + sellAmountUsd, + affiliateAddress: data.affiliateAddress || null, }, }); - this.logger.log(`Swap created: ${swap.id}${referralCode ? ` with referral code ${referralCode}` : ''}`); + this.logger.log( + `Swap created: ${swap.id}` + + `${referralCode ? ` with referral code ${referralCode}` : ''}` + + `${data.affiliateAddress ? ` with affiliate ${data.affiliateAddress}` : ''}` + + `${sellAmountUsd ? ` ($${sellAmountUsd})` : ''}`, + ); return swap; } catch (error) { this.logger.error('Failed to create swap', error); @@ -113,7 +159,19 @@ export class SwapsService { return num.replace(/\.?0+$/, ''); } - private async sendStatusUpdateNotification(swap: Pick) { + private async sendStatusUpdateNotification( + swap: Pick< + Swap, + | 'id' + | 'userId' + | 'status' + | 'sellAsset' + | 'buyAsset' + | 'sellAmountCryptoPrecision' + | 'actualBuyAmountCryptoPrecision' + | 'expectedBuyAmountCryptoPrecision' + >, + ) { let title: string; let body: string; let type: 'SWAP_STATUS_UPDATE' | 'SWAP_COMPLETED' | 'SWAP_FAILED'; @@ -124,7 +182,10 @@ export class SwapsService { switch (swap.status) { case 'SUCCESS': title = 'Swap Completed!'; - const buyAmount = this.formatAmount(swap.actualBuyAmountCryptoPrecision || swap.expectedBuyAmountCryptoPrecision); + const buyAmount = this.formatAmount( + swap.actualBuyAmountCryptoPrecision || + swap.expectedBuyAmountCryptoPrecision, + ); body = `Your swap of ${this.formatAmount(swap.sellAmountCryptoPrecision)} ${sellAsset.symbol} to ${buyAmount} ${buyAsset.symbol} is complete.`; type = 'SWAP_COMPLETED'; break; @@ -155,7 +216,7 @@ export class SwapsService { take: limit, }); - return swaps.map(swap => ({ + return swaps.map((swap) => ({ ...swap, sellAsset: swap.sellAsset as Asset, buyAsset: swap.buyAsset as Asset, @@ -173,7 +234,7 @@ export class SwapsService { }, }); - return swaps.map(swap => ({ + return swaps.map((swap) => ({ ...swap, sellAsset: swap.sellAsset, buyAsset: swap.buyAsset, @@ -189,15 +250,21 @@ export class SwapsService { }, }); - return swaps.map(swap => ({ + return swaps.map((swap) => ({ ...swap, sellAsset: swap.sellAsset, buyAsset: swap.buyAsset, })); } - async calculateReferralFees(referralCode: string, startDate?: Date, endDate?: Date) { - this.logger.log(`Calculating referral fees for code: ${referralCode}, period: ${startDate?.toISOString()} - ${endDate?.toISOString()}`); + async calculateReferralFees( + referralCode: string, + startDate?: Date, + endDate?: Date, + ) { + this.logger.log( + `Calculating referral fees for code: ${referralCode}, period: ${startDate?.toISOString()} - ${endDate?.toISOString()}`, + ); // Fetch swaps for the current period const periodWhereClause: any = { @@ -242,14 +309,18 @@ export class SwapsService { }, }); - this.logger.log(`Found ${periodSwaps.length} swaps for period, ${allTimeSwaps.length} swaps all-time for referral code ${referralCode}`); + this.logger.log( + `Found ${periodSwaps.length} swaps for period, ${allTimeSwaps.length} swaps all-time for referral code ${referralCode}`, + ); let periodFeesUsd = 0; let totalSwapVolumeUsd = 0; const swapCount = periodSwaps.length; // Import pricing utilities dynamically - const { getAssetPriceUsd, calculateUsdValue } = await import('../utils/pricing'); + const { getAssetPriceUsd, calculateUsdValue } = await import( + '../utils/pricing' + ); // Fetch prices for all unique assets from both period and all-time swaps const uniqueAssets = new Map(); @@ -261,10 +332,12 @@ export class SwapsService { } // Fetch all prices in parallel - const pricePromises = Array.from(uniqueAssets.values()).map(async (asset) => { - const price = await getAssetPriceUsd(asset); - return { assetId: asset.assetId, price }; - }); + const pricePromises = Array.from(uniqueAssets.values()).map( + async (asset) => { + const price = await getAssetPriceUsd(asset); + return { assetId: asset.assetId, price }; + }, + ); const prices = await Promise.all(pricePromises); const priceMap = new Map(); @@ -278,11 +351,15 @@ export class SwapsService { const price = priceMap.get(sellAsset.assetId); if (!price) { - this.logger.warn(`No price found for asset ${sellAsset.assetId}, skipping swap ${swap.swapId}`); + this.logger.warn( + `No price found for asset ${sellAsset.assetId}, skipping swap ${swap.swapId}`, + ); continue; } - const sellAmountUsd = parseFloat(calculateUsdValue(swap.sellAmountCryptoPrecision, price)); + const sellAmountUsd = parseFloat( + calculateUsdValue(swap.sellAmountCryptoPrecision, price), + ); totalSwapVolumeUsd += sellAmountUsd; // Extract affiliateBps from verification details @@ -304,7 +381,9 @@ export class SwapsService { if (!price) continue; - const sellAmountUsd = parseFloat(calculateUsdValue(swap.sellAmountCryptoPrecision, price)); + const sellAmountUsd = parseFloat( + calculateUsdValue(swap.sellAmountCryptoPrecision, price), + ); const verificationDetails = swap.affiliateVerificationDetails as any; const affiliateBps = verificationDetails?.affiliateBps; @@ -320,8 +399,8 @@ export class SwapsService { this.logger.log( `Referral fee calculation for ${referralCode}: ` + - `Period: ${swapCount} swaps, $${totalSwapVolumeUsd.toFixed(2)} volume, $${periodReferrerCommissionUsd.toFixed(2)} commission | ` + - `All-time: ${allTimeSwaps.length} swaps, $${allTimeReferrerCommissionUsd.toFixed(2)} total commission` + `Period: ${swapCount} swaps, $${totalSwapVolumeUsd.toFixed(2)} volume, $${periodReferrerCommissionUsd.toFixed(2)} commission | ` + + `All-time: ${allTimeSwaps.length} swaps, $${allTimeReferrerCommissionUsd.toFixed(2)} total commission`, ); return { @@ -335,14 +414,173 @@ export class SwapsService { }; } + async calculateAffiliateFees( + affiliateAddress: string, + startDate?: Date, + endDate?: Date, + ) { + this.logger.log( + `Calculating affiliate fees for address: ${affiliateAddress}, period: ${startDate?.toISOString()} - ${endDate?.toISOString()}`, + ); + + const periodWhereClause: any = { + affiliateAddress, + isAffiliateVerified: true, + status: 'SUCCESS', + }; + + if (startDate && endDate) { + periodWhereClause.createdAt = { + gte: startDate, + lte: endDate, + }; + } + + const periodSwaps = await this.prisma.swap.findMany({ + where: periodWhereClause, + select: { + id: true, + swapId: true, + sellAsset: true, + sellAmountCryptoPrecision: true, + sellAmountUsd: true, + affiliateVerificationDetails: true, + createdAt: true, + }, + }); + + const allTimeSwaps = await this.prisma.swap.findMany({ + where: { + affiliateAddress, + isAffiliateVerified: true, + status: 'SUCCESS', + }, + select: { + id: true, + swapId: true, + sellAsset: true, + sellAmountCryptoPrecision: true, + sellAmountUsd: true, + affiliateVerificationDetails: true, + createdAt: true, + }, + }); + + this.logger.log( + `Found ${periodSwaps.length} swaps for period, ${allTimeSwaps.length} swaps all-time for affiliate ${affiliateAddress}`, + ); + + let periodFeesUsd = 0; + let totalSwapVolumeUsd = 0; + const swapCount = periodSwaps.length; + + const { getAssetPriceUsd, calculateUsdValue } = await import( + '../utils/pricing' + ); + + const uniqueAssets = new Map(); + for (const swap of [...periodSwaps, ...allTimeSwaps]) { + const sellAsset = swap.sellAsset as Asset; + if (!uniqueAssets.has(sellAsset.assetId)) { + uniqueAssets.set(sellAsset.assetId, sellAsset); + } + } + + const pricePromises = Array.from(uniqueAssets.values()).map( + async (asset) => { + const price = await getAssetPriceUsd(asset); + return { assetId: asset.assetId, price }; + }, + ); + + const prices = await Promise.all(pricePromises); + const priceMap = new Map(); + prices.forEach(({ assetId, price }) => { + priceMap.set(assetId, price); + }); + + for (const swap of periodSwaps) { + const sellAsset = swap.sellAsset as Asset; + + let sellAmountUsd: number; + if (swap.sellAmountUsd) { + sellAmountUsd = parseFloat(swap.sellAmountUsd); + } else { + const price = priceMap.get(sellAsset.assetId); + if (!price) { + this.logger.warn( + `No price found for asset ${sellAsset.assetId}, skipping swap ${swap.swapId}`, + ); + continue; + } + sellAmountUsd = parseFloat( + calculateUsdValue(swap.sellAmountCryptoPrecision, price), + ); + } + + totalSwapVolumeUsd += sellAmountUsd; + + const verificationDetails = swap.affiliateVerificationDetails as any; + const affiliateBps = verificationDetails?.affiliateBps; + + if (affiliateBps && sellAmountUsd > 0) { + const feeUsd = (sellAmountUsd * affiliateBps) / 10000; + periodFeesUsd += feeUsd; + } + } + + let allTimeFeesUsd = 0; + for (const swap of allTimeSwaps) { + const sellAsset = swap.sellAsset as Asset; + + let sellAmountUsd: number; + if (swap.sellAmountUsd) { + sellAmountUsd = parseFloat(swap.sellAmountUsd); + } else { + const price = priceMap.get(sellAsset.assetId); + if (!price) continue; + sellAmountUsd = parseFloat( + calculateUsdValue(swap.sellAmountCryptoPrecision, price), + ); + } + + const verificationDetails = swap.affiliateVerificationDetails as any; + const affiliateBps = verificationDetails?.affiliateBps; + + if (affiliateBps && sellAmountUsd > 0) { + const feeUsd = (sellAmountUsd * affiliateBps) / 10000; + allTimeFeesUsd += feeUsd; + } + } + + const periodReferrerCommissionUsd = periodFeesUsd * 0.1; + const allTimeReferrerCommissionUsd = allTimeFeesUsd * 0.1; + + this.logger.log( + `Affiliate fee calculation for ${affiliateAddress}: ` + + `Period: ${swapCount} swaps, $${totalSwapVolumeUsd.toFixed(2)} volume, $${periodReferrerCommissionUsd.toFixed(2)} commission | ` + + `All-time: ${allTimeSwaps.length} swaps, $${allTimeReferrerCommissionUsd.toFixed(2)} total commission`, + ); + + return { + affiliateAddress, + swapCount, + totalSwapVolumeUsd: totalSwapVolumeUsd.toFixed(2), + totalFeesCollectedUsd: allTimeReferrerCommissionUsd.toFixed(2), + referrerCommissionUsd: periodReferrerCommissionUsd.toFixed(2), + periodStart: startDate?.toISOString(), + periodEnd: endDate?.toISOString(), + }; + } + async pollSwapStatus(swapId: string): Promise { try { this.logger.log(`Polling status for swap: ${swapId}`); - + const swap = await this.prisma.swap.findUnique({ where: { swapId }, }); - + if (!swap) { throw new Error(`Swap not found: ${swapId}`); } @@ -350,7 +588,7 @@ export class SwapsService { const sellAsset = swap.sellAsset as Asset; const swapper = swappers[swap.swapperName]; - + if (!swapper) { throw new Error(`Swapper not found: ${swap.swapperName}`); } @@ -371,9 +609,12 @@ export class SwapsService { }, stepIndex: 0, config: { - VITE_UNCHAINED_THORCHAIN_HTTP_URL: process.env.VITE_UNCHAINED_THORCHAIN_HTTP_URL || '', - VITE_UNCHAINED_MAYACHAIN_HTTP_URL: process.env.VITE_UNCHAINED_MAYACHAIN_HTTP_URL || '', - VITE_UNCHAINED_COSMOS_HTTP_URL: process.env.VITE_UNCHAINED_COSMOS_HTTP_URL || '', + VITE_UNCHAINED_THORCHAIN_HTTP_URL: + process.env.VITE_UNCHAINED_THORCHAIN_HTTP_URL || '', + VITE_UNCHAINED_MAYACHAIN_HTTP_URL: + process.env.VITE_UNCHAINED_MAYACHAIN_HTTP_URL || '', + VITE_UNCHAINED_COSMOS_HTTP_URL: + process.env.VITE_UNCHAINED_COSMOS_HTTP_URL || '', VITE_THORCHAIN_NODE_URL: process.env.VITE_THORCHAIN_NODE_URL || '', VITE_MAYACHAIN_NODE_URL: process.env.VITE_MAYACHAIN_NODE_URL || '', VITE_COWSWAP_BASE_URL: process.env.VITE_COWSWAP_BASE_URL || '', @@ -383,29 +624,46 @@ export class SwapsService { VITE_RELAY_API_URL: process.env.VITE_RELAY_API_URL || '', VITE_PORTALS_BASE_URL: process.env.VITE_PORTALS_BASE_URL || '', VITE_ZRX_BASE_URL: process.env.VITE_ZRX_BASE_URL || '', - VITE_THORCHAIN_MIDGARD_URL: process.env.VITE_THORCHAIN_MIDGARD_URL || '', - VITE_MAYACHAIN_MIDGARD_URL: process.env.VITE_MAYACHAIN_MIDGARD_URL || '', - VITE_UNCHAINED_BITCOIN_HTTP_URL: process.env.VITE_UNCHAINED_BITCOIN_HTTP_URL || '', - VITE_UNCHAINED_DOGECOIN_HTTP_URL: process.env.VITE_UNCHAINED_DOGECOIN_HTTP_URL || '', - VITE_UNCHAINED_LITECOIN_HTTP_URL: process.env.VITE_UNCHAINED_LITECOIN_HTTP_URL || '', - VITE_UNCHAINED_BITCOINCASH_HTTP_URL: process.env.VITE_UNCHAINED_BITCOINCASH_HTTP_URL || '', - VITE_UNCHAINED_ETHEREUM_HTTP_URL: process.env.VITE_UNCHAINED_ETHEREUM_HTTP_URL || '', - VITE_UNCHAINED_AVALANCHE_HTTP_URL: process.env.VITE_UNCHAINED_AVALANCHE_HTTP_URL || '', - VITE_UNCHAINED_BNBSMARTCHAIN_HTTP_URL: process.env.VITE_UNCHAINED_BNBSMARTCHAIN_HTTP_URL || '', - VITE_UNCHAINED_BASE_HTTP_URL: process.env.VITE_UNCHAINED_BASE_HTTP_URL || '', - VITE_NEAR_INTENTS_API_KEY: process.env.VITE_NEAR_INTENTS_API_KEY || '', + VITE_THORCHAIN_MIDGARD_URL: + process.env.VITE_THORCHAIN_MIDGARD_URL || '', + VITE_MAYACHAIN_MIDGARD_URL: + process.env.VITE_MAYACHAIN_MIDGARD_URL || '', + VITE_UNCHAINED_BITCOIN_HTTP_URL: + process.env.VITE_UNCHAINED_BITCOIN_HTTP_URL || '', + VITE_UNCHAINED_DOGECOIN_HTTP_URL: + process.env.VITE_UNCHAINED_DOGECOIN_HTTP_URL || '', + VITE_UNCHAINED_LITECOIN_HTTP_URL: + process.env.VITE_UNCHAINED_LITECOIN_HTTP_URL || '', + VITE_UNCHAINED_BITCOINCASH_HTTP_URL: + process.env.VITE_UNCHAINED_BITCOINCASH_HTTP_URL || '', + VITE_UNCHAINED_ETHEREUM_HTTP_URL: + process.env.VITE_UNCHAINED_ETHEREUM_HTTP_URL || '', + VITE_UNCHAINED_AVALANCHE_HTTP_URL: + process.env.VITE_UNCHAINED_AVALANCHE_HTTP_URL || '', + VITE_UNCHAINED_BNBSMARTCHAIN_HTTP_URL: + process.env.VITE_UNCHAINED_BNBSMARTCHAIN_HTTP_URL || '', + VITE_UNCHAINED_BASE_HTTP_URL: + process.env.VITE_UNCHAINED_BASE_HTTP_URL || '', + VITE_NEAR_INTENTS_API_KEY: + process.env.VITE_NEAR_INTENTS_API_KEY || '', VITE_FEATURE_THORCHAINSWAP_LONGTAIL: true, VITE_FEATURE_THORCHAINSWAP_L1_TO_LONGTAIL: true, VITE_FEATURE_CHAINFLIP_SWAP_DCA: true, }, assertGetSolanaChainAdapter: (chainId: ChainId) => { - return this.solanaChainAdapterService.assertGetSolanaChainAdapter(chainId); + return this.solanaChainAdapterService.assertGetSolanaChainAdapter( + chainId, + ); }, assertGetUtxoChainAdapter: (chainId: ChainId) => { - return this.utxoChainAdapterService.assertGetUtxoChainAdapter(chainId); + return this.utxoChainAdapterService.assertGetUtxoChainAdapter( + chainId, + ); }, assertGetCosmosSdkChainAdapter: (chainId: ChainId) => { - return this.cosmosSdkChainAdapterService.assertGetCosmosSdkChainAdapter(chainId); + return this.cosmosSdkChainAdapterService.assertGetCosmosSdkChainAdapter( + chainId, + ); }, assertGetEvmChainAdapter: (chainId: ChainId) => { return this.evmChainAdapterService.assertGetEvmChainAdapter(chainId); @@ -415,26 +673,35 @@ export class SwapsService { // Verify affiliate usage let isAffiliateVerified: boolean | undefined; - let affiliateVerificationDetails: { hasAffiliate: boolean; affiliateBps?: number; affiliateAddress?: string } | undefined; + let affiliateVerificationDetails: + | { + hasAffiliate: boolean; + affiliateBps?: number; + affiliateAddress?: string; + } + | undefined; try { // Enrich metadata with swap fields needed for verification const enrichedMetadata = { ...(swap.metadata as Record), receiveAddress: swap.receiveAddress, - expectedBuyAmountCryptoPrecision: swap.expectedBuyAmountCryptoPrecision, + expectedBuyAmountCryptoPrecision: + swap.expectedBuyAmountCryptoPrecision, createdAt: swap.createdAt.getTime(), }; - const verificationResult = await this.swapVerificationService.verifySwapAffiliate( - swapId, - swap.swapperName, - sellAsset.chainId, - swap.sellTxHash || undefined, - enrichedMetadata, - ); + const verificationResult = + await this.swapVerificationService.verifySwapAffiliate( + swapId, + swap.swapperName, + sellAsset.chainId, + swap.sellTxHash || undefined, + enrichedMetadata, + ); - isAffiliateVerified = verificationResult.isVerified && verificationResult.hasAffiliate; + isAffiliateVerified = + verificationResult.isVerified && verificationResult.hasAffiliate; if (verificationResult.isVerified) { affiliateVerificationDetails = { @@ -458,13 +725,20 @@ export class SwapsService { }, }); } catch (verificationError) { - this.logger.warn(`Failed to verify affiliate for swap ${swapId}:`, verificationError); + this.logger.warn( + `Failed to verify affiliate for swap ${swapId}:`, + verificationError, + ); // Don't fail the entire status check if verification fails } return { - status: status.status === 'Confirmed' ? 'SUCCESS' : - status.status === 'Failed' ? 'FAILED' : 'PENDING', + status: + status.status === 'Confirmed' + ? 'SUCCESS' + : status.status === 'Failed' + ? 'FAILED' + : 'PENDING', sellTxHash: swap.sellTxHash, buyTxHash: status.buyTxHash, statusMessage: status.message, diff --git a/packages/shared-types/src/index.ts b/packages/shared-types/src/index.ts index baf6642..9b6d972 100644 --- a/packages/shared-types/src/index.ts +++ b/packages/shared-types/src/index.ts @@ -2,7 +2,10 @@ import { Asset } from '@shapeshiftoss/types'; export type DeviceType = 'MOBILE' | 'WEB'; export type SwapStatus = 'IDLE' | 'PENDING' | 'SUCCESS' | 'FAILED'; -export type NotificationType = 'SWAP_STATUS_UPDATE' | 'SWAP_COMPLETED' | 'SWAP_FAILED'; +export type NotificationType = + | 'SWAP_STATUS_UPDATE' + | 'SWAP_COMPLETED' + | 'SWAP_FAILED'; export interface Device { id: string; @@ -77,6 +80,7 @@ export interface CreateSwapDto { receiveAddress?: string; isStreaming?: boolean; metadata?: Record; + affiliateAddress?: string; } export interface UpdateSwapStatusDto { From 0726ab16e36188fc46517e383c091d62c76515cc Mon Sep 17 00:00:00 2001 From: NeOMakinG <14963751+NeOMakinG@users.noreply.github.com> Date: Tue, 24 Feb 2026 13:41:19 +0100 Subject: [PATCH 2/6] feat: affiliate tracking with configurable BPS, origin field, on-chain verification, and full lint cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Swap service: affiliate address tracking, fee asset resolution, configurable BPS - Swap service: origin field (web/api/widget) with commission rate logic - Swap service: live price calculation with distribution cutoff (5th of month) - Verification service: per-protocol sell amount extraction (THORChain, Maya, CowSwap, Relay, Portals, Chainflip, 0x, Bebop, NEAR) - Verification service: min(verified, db) anti-inflation for fee calculation - Security: null origin returns 0% commission (safe default) - Prisma schema: affiliateBps, origin, affiliateFeeAssetId, affiliateFeeAmountCryptoBaseUnit - Lint: 277 errors → 0 across all services (proper TypeScript interfaces for external API responses, removed unnecessary async, typed Prisma queries) --- apps/notifications-service/src/app.module.js | 32 + apps/notifications-service/src/main.js | 22 + .../notifications/notifications.controller.js | 88 ++ .../notifications/notifications.controller.ts | 61 +- .../notifications/notifications.service.js | 201 ++++ .../notifications/notifications.service.ts | 103 ++- .../src/prisma/prisma.service.js | 23 + .../src/prisma/prisma.service.ts | 5 +- .../src/websocket/websocket.gateway.js | 80 ++ .../src/websocket/websocket.gateway.ts | 32 +- apps/swap-service/prisma/schema.prisma | 4 + apps/swap-service/src/app.module.js | 53 ++ .../src/lib/chain-adapter-init.service.js | 55 ++ .../src/lib/chain-adapter-init.service.ts | 16 +- .../src/lib/chain-adapter-manager.service.js | 25 + .../lib/chain-adapters/cosmos-sdk.service.js | 132 +++ .../lib/chain-adapters/cosmos-sdk.service.ts | 46 +- .../src/lib/chain-adapters/evm.service.js | 220 +++++ .../src/lib/chain-adapters/evm.service.ts | 73 +- .../src/lib/chain-adapters/solana.service.js | 94 ++ .../src/lib/chain-adapters/solana.service.ts | 14 +- .../src/lib/chain-adapters/utxo.service.js | 145 +++ .../src/lib/chain-adapters/utxo.service.ts | 33 +- apps/swap-service/src/main.js | 26 + .../src/polling/swap-polling.service.js | 69 ++ .../src/polling/swap-polling.service.ts | 21 +- .../swap-service/src/prisma/prisma.service.js | 23 + .../swap-service/src/prisma/prisma.service.ts | 5 +- .../src/swaps/swaps.controller.js | 162 ++++ apps/swap-service/src/swaps/swaps.service.js | 708 +++++++++++++++ apps/swap-service/src/swaps/swaps.service.ts | 357 ++++++-- .../src/utils/affiliateFeeAsset.js | 57 ++ .../src/utils/affiliateFeeAsset.ts | 71 ++ apps/swap-service/src/utils/pricing.js | 59 ++ apps/swap-service/src/utils/pricing.ts | 13 +- .../verification/swap-verification.service.js | 859 ++++++++++++++++++ .../verification/swap-verification.service.ts | 543 +++++++++-- .../src/websocket/websocket.gateway.js | 80 ++ .../src/websocket/websocket.gateway.ts | 37 +- apps/user-service/src/app.module.js | 31 + apps/user-service/src/main.js | 22 + .../user-service/src/prisma/prisma.service.js | 23 + .../user-service/src/prisma/prisma.service.ts | 5 +- .../src/referral/referral.controller.js | 135 +++ .../src/referral/referral.controller.ts | 19 +- .../src/referral/referral.service.js | 244 +++++ .../src/referral/referral.service.ts | 83 +- .../src/users/users.controller.js | 159 ++++ .../src/users/users.controller.ts | 19 +- apps/user-service/src/users/users.service.js | 347 +++++++ apps/user-service/src/users/users.service.ts | 130 ++- packages/shared-types/src/index.js | 2 + packages/shared-types/src/index.ts | 7 +- packages/shared-utils/src/index.js | 115 +++ packages/shared-utils/src/index.ts | 42 +- packages/shared-utils/src/service-clients.js | 106 +++ packages/shared-utils/src/service-clients.ts | 47 +- 57 files changed, 5726 insertions(+), 457 deletions(-) create mode 100644 apps/notifications-service/src/app.module.js create mode 100644 apps/notifications-service/src/main.js create mode 100644 apps/notifications-service/src/notifications/notifications.controller.js create mode 100644 apps/notifications-service/src/notifications/notifications.service.js create mode 100644 apps/notifications-service/src/prisma/prisma.service.js create mode 100644 apps/notifications-service/src/websocket/websocket.gateway.js create mode 100644 apps/swap-service/src/app.module.js create mode 100644 apps/swap-service/src/lib/chain-adapter-init.service.js create mode 100644 apps/swap-service/src/lib/chain-adapter-manager.service.js create mode 100644 apps/swap-service/src/lib/chain-adapters/cosmos-sdk.service.js create mode 100644 apps/swap-service/src/lib/chain-adapters/evm.service.js create mode 100644 apps/swap-service/src/lib/chain-adapters/solana.service.js create mode 100644 apps/swap-service/src/lib/chain-adapters/utxo.service.js create mode 100644 apps/swap-service/src/main.js create mode 100644 apps/swap-service/src/polling/swap-polling.service.js create mode 100644 apps/swap-service/src/prisma/prisma.service.js create mode 100644 apps/swap-service/src/swaps/swaps.controller.js create mode 100644 apps/swap-service/src/swaps/swaps.service.js create mode 100644 apps/swap-service/src/utils/affiliateFeeAsset.js create mode 100644 apps/swap-service/src/utils/affiliateFeeAsset.ts create mode 100644 apps/swap-service/src/utils/pricing.js create mode 100644 apps/swap-service/src/verification/swap-verification.service.js create mode 100644 apps/swap-service/src/websocket/websocket.gateway.js create mode 100644 apps/user-service/src/app.module.js create mode 100644 apps/user-service/src/main.js create mode 100644 apps/user-service/src/prisma/prisma.service.js create mode 100644 apps/user-service/src/referral/referral.controller.js create mode 100644 apps/user-service/src/referral/referral.service.js create mode 100644 apps/user-service/src/users/users.controller.js create mode 100644 apps/user-service/src/users/users.service.js create mode 100644 packages/shared-types/src/index.js create mode 100644 packages/shared-utils/src/index.js create mode 100644 packages/shared-utils/src/service-clients.js diff --git a/apps/notifications-service/src/app.module.js b/apps/notifications-service/src/app.module.js new file mode 100644 index 0000000..9e15a0c --- /dev/null +++ b/apps/notifications-service/src/app.module.js @@ -0,0 +1,32 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AppModule = void 0; +const common_1 = require("@nestjs/common"); +const config_1 = require("@nestjs/config"); +const axios_1 = require("@nestjs/axios"); +const notifications_controller_1 = require("./notifications/notifications.controller"); +const notifications_service_1 = require("./notifications/notifications.service"); +const websocket_gateway_1 = require("./websocket/websocket.gateway"); +const prisma_service_1 = require("./prisma/prisma.service"); +let AppModule = class AppModule { +}; +exports.AppModule = AppModule; +exports.AppModule = AppModule = __decorate([ + (0, common_1.Module)({ + imports: [ + config_1.ConfigModule.forRoot({ + isGlobal: true, + envFilePath: '../.env', + }), + axios_1.HttpModule, + ], + controllers: [notifications_controller_1.NotificationsController], + providers: [notifications_service_1.NotificationsService, websocket_gateway_1.WebsocketGateway, prisma_service_1.PrismaService], + }) +], AppModule); diff --git a/apps/notifications-service/src/main.js b/apps/notifications-service/src/main.js new file mode 100644 index 0000000..b3ce02f --- /dev/null +++ b/apps/notifications-service/src/main.js @@ -0,0 +1,22 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const core_1 = require("@nestjs/core"); +const app_module_1 = require("./app.module"); +async function bootstrap() { + const app = await core_1.NestFactory.create(app_module_1.AppModule); + // Enable CORS + app.enableCors({ + origin: process.env.ALLOWED_ORIGINS?.split(',') || [ + 'http://localhost:3000', + /\.shapeshift\.com$/, + ], + credentials: true, + }); + app.getHttpAdapter().get('/health', (_, res) => { + res.status(200).json({ status: 'ok' }); + }); + const port = process.env.PORT || 3000; + await app.listen(port); + console.log(`Notifications service is running on: http://localhost:${port}`); +} +bootstrap(); diff --git a/apps/notifications-service/src/notifications/notifications.controller.js b/apps/notifications-service/src/notifications/notifications.controller.js new file mode 100644 index 0000000..5c84754 --- /dev/null +++ b/apps/notifications-service/src/notifications/notifications.controller.js @@ -0,0 +1,88 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.NotificationsController = void 0; +const common_1 = require("@nestjs/common"); +const notifications_service_1 = require("./notifications.service"); +let NotificationsController = class NotificationsController { + constructor(notificationsService) { + this.notificationsService = notificationsService; + } + async createNotification(data) { + return this.notificationsService.createNotification(data); + } + async registerDevice(data) { + return this.notificationsService.registerDevice(data.userId, data.deviceToken, data.deviceType); + } + async getUserNotifications(userId, limit) { + return this.notificationsService.getUserNotifications(userId, limit ? parseInt(limit) : 50); + } + async getUserDevices(userId) { + return this.notificationsService.getUserDevices(userId); + } + async sendToUser(data) { + return this.notificationsService.sendPushNotificationToUser(data.userId, data.title, data.body, data.data); + } + async sendToDevice(data) { + return this.notificationsService.sendPushNotificationToDevice(data.deviceToken, data.title, data.body, data.data); + } +}; +exports.NotificationsController = NotificationsController; +__decorate([ + (0, common_1.Post)(), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], NotificationsController.prototype, "createNotification", null); +__decorate([ + (0, common_1.Post)('register-device'), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], NotificationsController.prototype, "registerDevice", null); +__decorate([ + (0, common_1.Get)('user/:userId'), + __param(0, (0, common_1.Param)('userId')), + __param(1, (0, common_1.Query)('limit')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String, String]), + __metadata("design:returntype", Promise) +], NotificationsController.prototype, "getUserNotifications", null); +__decorate([ + (0, common_1.Get)('devices/:userId'), + __param(0, (0, common_1.Param)('userId')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", Promise) +], NotificationsController.prototype, "getUserDevices", null); +__decorate([ + (0, common_1.Post)('send-to-user'), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], NotificationsController.prototype, "sendToUser", null); +__decorate([ + (0, common_1.Post)('send-to-device'), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], NotificationsController.prototype, "sendToDevice", null); +exports.NotificationsController = NotificationsController = __decorate([ + (0, common_1.Controller)('notifications'), + __metadata("design:paramtypes", [notifications_service_1.NotificationsService]) +], NotificationsController); diff --git a/apps/notifications-service/src/notifications/notifications.controller.ts b/apps/notifications-service/src/notifications/notifications.controller.ts index 77b947d..cb299c5 100644 --- a/apps/notifications-service/src/notifications/notifications.controller.ts +++ b/apps/notifications-service/src/notifications/notifications.controller.ts @@ -1,25 +1,36 @@ -import { Controller, Post, Body, Get, Param, Query, Put } from '@nestjs/common'; +import { Controller, Post, Body, Get, Param, Query } from '@nestjs/common'; import { NotificationsService } from './notifications.service'; -import { NotificationType } from '@shapeshift/shared-types'; +import { + NotificationType, + PushNotificationData, +} from '@shapeshift/shared-types'; @Controller('notifications') export class NotificationsController { constructor(private readonly notificationsService: NotificationsService) {} @Post() - async createNotification(@Body() data: { - userId: string; - title: string; - body: string; - type: NotificationType; - swapId?: string; - }) { + async createNotification( + @Body() + data: { + userId: string; + title: string; + body: string; + type: NotificationType; + swapId?: string; + }, + ) { return this.notificationsService.createNotification(data); } @Post('register-device') async registerDevice( - @Body() data: { userId: string; deviceToken: string; deviceType: 'MOBILE' | 'WEB' }, + @Body() + data: { + userId: string; + deviceToken: string; + deviceType: 'MOBILE' | 'WEB'; + }, ) { return this.notificationsService.registerDevice( data.userId, @@ -45,12 +56,15 @@ export class NotificationsController { } @Post('send-to-user') - async sendToUser(@Body() data: { - userId: string; - title: string; - body: string; - data?: any; - }) { + async sendToUser( + @Body() + data: { + userId: string; + title: string; + body: string; + data?: PushNotificationData; + }, + ) { return this.notificationsService.sendPushNotificationToUser( data.userId, data.title, @@ -60,12 +74,15 @@ export class NotificationsController { } @Post('send-to-device') - async sendToDevice(@Body() data: { - deviceToken: string; - title: string; - body: string; - data?: any; - }) { + async sendToDevice( + @Body() + data: { + deviceToken: string; + title: string; + body: string; + data?: PushNotificationData; + }, + ) { return this.notificationsService.sendPushNotificationToDevice( data.deviceToken, data.title, diff --git a/apps/notifications-service/src/notifications/notifications.service.js b/apps/notifications-service/src/notifications/notifications.service.js new file mode 100644 index 0000000..6d76762 --- /dev/null +++ b/apps/notifications-service/src/notifications/notifications.service.js @@ -0,0 +1,201 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var NotificationsService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.NotificationsService = void 0; +const common_1 = require("@nestjs/common"); +const axios_1 = require("@nestjs/axios"); +const expo_server_sdk_1 = require("expo-server-sdk"); +const prisma_service_1 = require("../prisma/prisma.service"); +const shared_utils_1 = require("@shapeshift/shared-utils"); +let NotificationsService = NotificationsService_1 = class NotificationsService { + constructor(prisma, httpService) { + this.prisma = prisma; + this.httpService = httpService; + this.logger = new common_1.Logger(NotificationsService_1.name); + this.expo = new expo_server_sdk_1.Expo({ + accessToken: (0, shared_utils_1.getRequiredEnvVar)('EXPO_ACCESS_TOKEN'), + }); + } + async createNotification(data) { + try { + const notification = await this.prisma.notification.create({ + data: { + userId: data.userId, + title: data.title, + body: data.body, + type: data.type, + swapId: data.swapId, + }, + }); + // Send push notification to all user devices + await this.sendPushNotification(notification); + return notification; + } + catch (error) { + this.logger.error('Failed to create notification', error); + throw error; + } + } + async sendPushNotification(notification) { + try { + // Get user devices from user service + const userServiceUrl = (0, shared_utils_1.getRequiredEnvVar)('USER_SERVICE_URL'); + const response = await this.httpService.axiosRef.get(`${userServiceUrl}/users/${notification.userId}/devices`); + const devices = response.data; + const activeDevices = devices.filter((device) => device.isActive); + if (activeDevices.length === 0) { + throw new common_1.BadRequestException(`No active devices found for user ${notification.userId}`); + } + const messages = activeDevices + .filter((device) => device.deviceType === 'MOBILE') + .map((device) => ({ + to: device.deviceToken, + sound: 'default', + title: notification.title, + body: notification.body, + data: { + notificationId: notification.id, + type: notification.type, + swapId: notification.swapId, + }, + priority: 'high', + channelId: 'swap-notifications', + })); + const tickets = await this.sendExpoPushNotifications(messages, notification.id); + return tickets; + } + catch (error) { + this.logger.error('Failed to send push notification', error); + throw new common_1.BadRequestException('Failed to send push notification'); + } + } + async sendPushNotificationToDevice(deviceToken, title, body, data) { + try { + if (!expo_server_sdk_1.Expo.isExpoPushToken(deviceToken)) { + throw new common_1.BadRequestException(`Invalid Expo push token: ${String(deviceToken)}`); + } + const message = { + to: deviceToken, + sound: 'default', + title, + body, + data: data || {}, + priority: 'high', + channelId: 'swap-notifications', + }; + const tickets = await this.sendExpoPushNotifications([message]); + return tickets; + } + catch (error) { + this.logger.error('Failed to send push notification to device', error); + throw new common_1.BadRequestException('Failed to send push notification to device'); + } + } + async sendPushNotificationToUser(userId, title, body, data) { + try { + // Get user devices from user service + const userServiceUrl = (0, shared_utils_1.getRequiredEnvVar)('USER_SERVICE_URL'); + const response = await this.httpService.axiosRef.get(`${userServiceUrl}/users/${userId}/devices`); + const devices = response.data; + const activeDevices = devices.filter((device) => device.isActive); + if (activeDevices.length === 0) { + throw new common_1.BadRequestException(`No active devices found for user ${userId}`); + } + const messages = activeDevices.map((device) => ({ + to: device.deviceToken, + sound: 'default', + title, + body, + data: data || {}, + priority: 'high', + channelId: 'swap-notifications', + })); + const tickets = await this.sendExpoPushNotifications(messages); + return tickets; + } + catch { + throw new common_1.BadRequestException('Failed to send push notification to user'); + } + } + async sendExpoPushNotifications(messages, notificationId) { + const chunks = this.expo.chunkPushNotifications(messages); + const tickets = []; + for (const chunk of chunks) { + try { + const ticketChunk = await this.expo.sendPushNotificationsAsync(chunk); + tickets.push(...ticketChunk); + } + catch (error) { + this.logger.error('Error sending chunk', error); + } + } + // Update notification with delivery timestamp if notificationId is provided + if (notificationId) { + await this.prisma.notification.update({ + where: { id: notificationId }, + data: { deliveredAt: new Date() }, + }); + } + this.logger.log(`Sent ${tickets.length} push notifications`); + return tickets; + } + async registerDevice(userId, deviceToken, deviceType) { + try { + this.logger.log(`registerDevice called with userId: ${userId}, deviceType: ${deviceType}, deviceToken: ${deviceToken}`); + // Only validate Expo push token for mobile devices + if (deviceType === 'MOBILE' && !expo_server_sdk_1.Expo.isExpoPushToken(deviceToken)) { + throw new common_1.BadRequestException('Invalid Expo push token'); + } + // For web devices, we expect a websocket channel identifier + if (deviceType === 'WEB' && !deviceToken) { + throw new common_1.BadRequestException('Invalid websocket channel identifier'); + } + // Register device with user service + const userServiceUrl = (0, shared_utils_1.getRequiredEnvVar)('USER_SERVICE_URL'); + const response = await this.httpService.axiosRef.post(`${userServiceUrl}/users/${userId}/devices`, { + deviceToken, + deviceType, + }); + const device = response.data; + this.logger.log(`Device registered: ${deviceToken} for user ${userId} (${deviceType})`); + return device; + } + catch (error) { + this.logger.error('Failed to register device', error); + throw new common_1.BadRequestException('Failed to register device'); + } + } + async getUserNotifications(userId, limit = 50) { + return this.prisma.notification.findMany({ + where: { userId }, + orderBy: { sentAt: 'desc' }, + take: limit, + }); + } + async getUserDevices(userId) { + try { + const userServiceUrl = (0, shared_utils_1.getRequiredEnvVar)('USER_SERVICE_URL'); + const response = await this.httpService.axiosRef.get(`${userServiceUrl}/users/${userId}/devices`); + return response.data; + } + catch (error) { + this.logger.error('Failed to get user devices', error); + throw new common_1.BadRequestException('Failed to get user devices'); + } + } +}; +exports.NotificationsService = NotificationsService; +exports.NotificationsService = NotificationsService = NotificationsService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService, + axios_1.HttpService]) +], NotificationsService); diff --git a/apps/notifications-service/src/notifications/notifications.service.ts b/apps/notifications-service/src/notifications/notifications.service.ts index 186ee3e..097457a 100644 --- a/apps/notifications-service/src/notifications/notifications.service.ts +++ b/apps/notifications-service/src/notifications/notifications.service.ts @@ -3,14 +3,13 @@ import { HttpService } from '@nestjs/axios'; import { Expo, ExpoPushMessage, ExpoPushTicket } from 'expo-server-sdk'; import { PrismaService } from '../prisma/prisma.service'; import { getRequiredEnvVar } from '@shapeshift/shared-utils'; -import { +import { CreateNotificationDto, Device, - PushNotificationData + PushNotificationData, } from '@shapeshift/shared-types'; import { Notification } from '@prisma/client'; - @Injectable() export class NotificationsService { private readonly logger = new Logger(NotificationsService.name); @@ -18,9 +17,11 @@ export class NotificationsService { constructor( private prisma: PrismaService, - private httpService: HttpService + private httpService: HttpService, ) { - this.expo = new Expo({ accessToken: getRequiredEnvVar('EXPO_ACCESS_TOKEN') }); + this.expo = new Expo({ + accessToken: getRequiredEnvVar('EXPO_ACCESS_TOKEN'), + }); } async createNotification(data: CreateNotificationDto): Promise { @@ -49,12 +50,16 @@ export class NotificationsService { try { // Get user devices from user service const userServiceUrl = getRequiredEnvVar('USER_SERVICE_URL'); - const response = await this.httpService.axiosRef.get(`${userServiceUrl}/users/${notification.userId}/devices`); + const response = await this.httpService.axiosRef.get( + `${userServiceUrl}/users/${notification.userId}/devices`, + ); const devices = response.data; const activeDevices = devices.filter((device) => device.isActive); - + if (activeDevices.length === 0) { - throw new BadRequestException(`No active devices found for user ${notification.userId}`); + throw new BadRequestException( + `No active devices found for user ${notification.userId}`, + ); } const messages: ExpoPushMessage[] = activeDevices @@ -73,7 +78,10 @@ export class NotificationsService { channelId: 'swap-notifications', })); - const tickets = await this.sendExpoPushNotifications(messages, notification.id); + const tickets = await this.sendExpoPushNotifications( + messages, + notification.id, + ); return tickets; } catch (error) { this.logger.error('Failed to send push notification', error); @@ -82,14 +90,16 @@ export class NotificationsService { } async sendPushNotificationToDevice( - deviceToken: string, - title: string, - body: string, - data?: PushNotificationData + deviceToken: string, + title: string, + body: string, + data?: PushNotificationData, ): Promise { try { if (!Expo.isExpoPushToken(deviceToken)) { - throw new BadRequestException(`Invalid Expo push token: ${deviceToken}`); + throw new BadRequestException( + `Invalid Expo push token: ${String(deviceToken)}`, + ); } const message: ExpoPushMessage = { @@ -106,28 +116,34 @@ export class NotificationsService { return tickets; } catch (error) { this.logger.error('Failed to send push notification to device', error); - throw new BadRequestException('Failed to send push notification to device'); + throw new BadRequestException( + 'Failed to send push notification to device', + ); } } async sendPushNotificationToUser( - userId: string, - title: string, - body: string, - data?: PushNotificationData + userId: string, + title: string, + body: string, + data?: PushNotificationData, ): Promise { try { // Get user devices from user service const userServiceUrl = getRequiredEnvVar('USER_SERVICE_URL'); - const response = await this.httpService.axiosRef.get(`${userServiceUrl}/users/${userId}/devices`); + const response = await this.httpService.axiosRef.get( + `${userServiceUrl}/users/${userId}/devices`, + ); const devices = response.data; const activeDevices = devices.filter((device) => device.isActive); if (activeDevices.length === 0) { - throw new BadRequestException(`No active devices found for user ${userId}`); + throw new BadRequestException( + `No active devices found for user ${userId}`, + ); } - const messages: ExpoPushMessage[] = activeDevices.map((device: any) => ({ + const messages: ExpoPushMessage[] = activeDevices.map((device) => ({ to: device.deviceToken, sound: 'default', title, @@ -139,12 +155,15 @@ export class NotificationsService { const tickets = await this.sendExpoPushNotifications(messages); return tickets; - } catch (error) { + } catch { throw new BadRequestException('Failed to send push notification to user'); } } - private async sendExpoPushNotifications(messages: ExpoPushMessage[], notificationId?: string): Promise { + private async sendExpoPushNotifications( + messages: ExpoPushMessage[], + notificationId?: string, + ): Promise { const chunks = this.expo.chunkPushNotifications(messages); const tickets: ExpoPushTicket[] = []; @@ -170,10 +189,16 @@ export class NotificationsService { return tickets; } - async registerDevice(userId: string, deviceToken: string, deviceType: 'MOBILE' | 'WEB') { + async registerDevice( + userId: string, + deviceToken: string, + deviceType: 'MOBILE' | 'WEB', + ) { try { - this.logger.log(`registerDevice called with userId: ${userId}, deviceType: ${deviceType}, deviceToken: ${deviceToken}`); - + this.logger.log( + `registerDevice called with userId: ${userId}, deviceType: ${deviceType}, deviceToken: ${deviceToken}`, + ); + // Only validate Expo push token for mobile devices if (deviceType === 'MOBILE' && !Expo.isExpoPushToken(deviceToken)) { throw new BadRequestException('Invalid Expo push token'); @@ -186,12 +211,17 @@ export class NotificationsService { // Register device with user service const userServiceUrl = getRequiredEnvVar('USER_SERVICE_URL'); - const response = await this.httpService.axiosRef.post(`${userServiceUrl}/users/${userId}/devices`, { - deviceToken, - deviceType, - }); + const response = await this.httpService.axiosRef.post( + `${userServiceUrl}/users/${userId}/devices`, + { + deviceToken, + deviceType, + }, + ); const device = response.data; - this.logger.log(`Device registered: ${deviceToken} for user ${userId} (${deviceType})`); + this.logger.log( + `Device registered: ${deviceToken} for user ${userId} (${deviceType})`, + ); return device; } catch (error) { this.logger.error('Failed to register device', error); @@ -199,7 +229,10 @@ export class NotificationsService { } } - async getUserNotifications(userId: string, limit = 50): Promise { + async getUserNotifications( + userId: string, + limit = 50, + ): Promise { return this.prisma.notification.findMany({ where: { userId }, orderBy: { sentAt: 'desc' }, @@ -210,7 +243,9 @@ export class NotificationsService { async getUserDevices(userId: string): Promise { try { const userServiceUrl = getRequiredEnvVar('USER_SERVICE_URL'); - const response = await this.httpService.axiosRef.get(`${userServiceUrl}/users/${userId}/devices`); + const response = await this.httpService.axiosRef.get( + `${userServiceUrl}/users/${userId}/devices`, + ); return response.data; } catch (error) { this.logger.error('Failed to get user devices', error); diff --git a/apps/notifications-service/src/prisma/prisma.service.js b/apps/notifications-service/src/prisma/prisma.service.js new file mode 100644 index 0000000..2b91c98 --- /dev/null +++ b/apps/notifications-service/src/prisma/prisma.service.js @@ -0,0 +1,23 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PrismaService = void 0; +const common_1 = require("@nestjs/common"); +const client_1 = require("@prisma/client"); +let PrismaService = class PrismaService extends client_1.PrismaClient { + async onModuleInit() { + await this.$connect(); + } + async onModuleDestroy() { + await this.$disconnect(); + } +}; +exports.PrismaService = PrismaService; +exports.PrismaService = PrismaService = __decorate([ + (0, common_1.Injectable)() +], PrismaService); diff --git a/apps/notifications-service/src/prisma/prisma.service.ts b/apps/notifications-service/src/prisma/prisma.service.ts index bb6565f..7ffd32d 100644 --- a/apps/notifications-service/src/prisma/prisma.service.ts +++ b/apps/notifications-service/src/prisma/prisma.service.ts @@ -2,7 +2,10 @@ import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; @Injectable() -export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { +export class PrismaService + extends PrismaClient + implements OnModuleInit, OnModuleDestroy +{ async onModuleInit() { await this.$connect(); } diff --git a/apps/notifications-service/src/websocket/websocket.gateway.js b/apps/notifications-service/src/websocket/websocket.gateway.js new file mode 100644 index 0000000..baad0e6 --- /dev/null +++ b/apps/notifications-service/src/websocket/websocket.gateway.js @@ -0,0 +1,80 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +var WebsocketGateway_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.WebsocketGateway = void 0; +const websockets_1 = require("@nestjs/websockets"); +const socket_io_1 = require("socket.io"); +const common_1 = require("@nestjs/common"); +const notifications_service_1 = require("../notifications/notifications.service"); +let WebsocketGateway = WebsocketGateway_1 = class WebsocketGateway { + constructor(notificationsService) { + this.notificationsService = notificationsService; + this.logger = new common_1.Logger(WebsocketGateway_1.name); + this.connectedClients = new Map(); + } + handleConnection(client) { + this.logger.log(`Client connected: ${client.id}`); + } + handleDisconnect(client) { + this.logger.log(`Client disconnected: ${client.id}`); + if (client.userId) { + this.connectedClients.delete(client.userId); + } + } + async handleGetNotifications(data, client) { + if (!client.userId) { + return { error: 'Not authenticated' }; + } + try { + const notifications = await this.notificationsService.getUserNotifications(client.userId, data.limit || 50); + return { success: true, notifications }; + } + catch (error) { + this.logger.error('Failed to get notifications', error); + return { error: 'Failed to get notifications' }; + } + } + async sendNotificationToUser(userId, notification) { + const client = this.connectedClients.get(userId); + if (client) { + client.emit('notification', notification); + } + this.server.to(`user:${userId}`).emit('notification', notification); + } + broadcastToAll(event, data) { + this.server.emit(event, data); + } +}; +exports.WebsocketGateway = WebsocketGateway; +__decorate([ + (0, websockets_1.WebSocketServer)(), + __metadata("design:type", socket_io_1.Server) +], WebsocketGateway.prototype, "server", void 0); +__decorate([ + (0, websockets_1.SubscribeMessage)('getNotifications'), + __param(0, (0, websockets_1.MessageBody)()), + __param(1, (0, websockets_1.ConnectedSocket)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", Promise) +], WebsocketGateway.prototype, "handleGetNotifications", null); +exports.WebsocketGateway = WebsocketGateway = WebsocketGateway_1 = __decorate([ + (0, websockets_1.WebSocketGateway)({ + cors: { + origin: '*', + }, + }), + __metadata("design:paramtypes", [notifications_service_1.NotificationsService]) +], WebsocketGateway); diff --git a/apps/notifications-service/src/websocket/websocket.gateway.ts b/apps/notifications-service/src/websocket/websocket.gateway.ts index 9412bdb..9096777 100644 --- a/apps/notifications-service/src/websocket/websocket.gateway.ts +++ b/apps/notifications-service/src/websocket/websocket.gateway.ts @@ -20,7 +20,9 @@ interface AuthenticatedSocket extends Socket { origin: '*', }, }) -export class WebsocketGateway implements OnGatewayConnection, OnGatewayDisconnect { +export class WebsocketGateway + implements OnGatewayConnection, OnGatewayDisconnect +{ @WebSocketServer() server: Server; @@ -50,10 +52,11 @@ export class WebsocketGateway implements OnGatewayConnection, OnGatewayDisconnec } try { - const notifications = await this.notificationsService.getUserNotifications( - client.userId, - data.limit || 50, - ); + const notifications = + await this.notificationsService.getUserNotifications( + client.userId, + data.limit || 50, + ); return { success: true, notifications }; } catch (error) { this.logger.error('Failed to get notifications', error); @@ -61,18 +64,21 @@ export class WebsocketGateway implements OnGatewayConnection, OnGatewayDisconnec } } - async sendNotificationToUser(userId: string, notification: { - id: string; - title: string; - body: string; - type: string; - swapId?: string; - }) { + sendNotificationToUser( + userId: string, + notification: { + id: string; + title: string; + body: string; + type: string; + swapId?: string; + }, + ) { const client = this.connectedClients.get(userId); if (client) { client.emit('notification', notification); } - + this.server.to(`user:${userId}`).emit('notification', notification); } diff --git a/apps/swap-service/prisma/schema.prisma b/apps/swap-service/prisma/schema.prisma index 9aa2081..13b8793 100644 --- a/apps/swap-service/prisma/schema.prisma +++ b/apps/swap-service/prisma/schema.prisma @@ -46,6 +46,10 @@ model Swap { affiliateVerificationDetails Json? affiliateVerifiedAt DateTime? affiliateAddress String? + affiliateBps String? + origin String? + affiliateFeeAssetId String? + affiliateFeeAmountCryptoBaseUnit String? @@index([referralCode]) @@index([affiliateAddress]) diff --git a/apps/swap-service/src/app.module.js b/apps/swap-service/src/app.module.js new file mode 100644 index 0000000..c47adff --- /dev/null +++ b/apps/swap-service/src/app.module.js @@ -0,0 +1,53 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AppModule = void 0; +const common_1 = require("@nestjs/common"); +const schedule_1 = require("@nestjs/schedule"); +const axios_1 = require("@nestjs/axios"); +const prisma_service_1 = require("./prisma/prisma.service"); +const swaps_controller_1 = require("./swaps/swaps.controller"); +const swaps_service_1 = require("./swaps/swaps.service"); +const swap_polling_service_1 = require("./polling/swap-polling.service"); +const swap_verification_service_1 = require("./verification/swap-verification.service"); +const websocket_gateway_1 = require("./websocket/websocket.gateway"); +const chain_adapter_init_service_1 = require("./lib/chain-adapter-init.service"); +const chain_adapter_manager_service_1 = require("./lib/chain-adapter-manager.service"); +const evm_service_1 = require("./lib/chain-adapters/evm.service"); +const utxo_service_1 = require("./lib/chain-adapters/utxo.service"); +const cosmos_sdk_service_1 = require("./lib/chain-adapters/cosmos-sdk.service"); +const solana_service_1 = require("./lib/chain-adapters/solana.service"); +const config_1 = require("@nestjs/config"); +let AppModule = class AppModule { +}; +exports.AppModule = AppModule; +exports.AppModule = AppModule = __decorate([ + (0, common_1.Module)({ + imports: [ + schedule_1.ScheduleModule.forRoot(), + axios_1.HttpModule, + config_1.ConfigModule.forRoot({ + envFilePath: '../../.env', + }), + ], + controllers: [swaps_controller_1.SwapsController], + providers: [ + prisma_service_1.PrismaService, + swaps_service_1.SwapsService, + swap_polling_service_1.SwapPollingService, + swap_verification_service_1.SwapVerificationService, + websocket_gateway_1.WebsocketGateway, + chain_adapter_init_service_1.ChainAdapterInitService, + chain_adapter_manager_service_1.ChainAdapterManagerService, + evm_service_1.EvmChainAdapterService, + utxo_service_1.UtxoChainAdapterService, + cosmos_sdk_service_1.CosmosSdkChainAdapterService, + solana_service_1.SolanaChainAdapterService, + ], + }) +], AppModule); diff --git a/apps/swap-service/src/lib/chain-adapter-init.service.js b/apps/swap-service/src/lib/chain-adapter-init.service.js new file mode 100644 index 0000000..7b476ca --- /dev/null +++ b/apps/swap-service/src/lib/chain-adapter-init.service.js @@ -0,0 +1,55 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var ChainAdapterInitService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ChainAdapterInitService = void 0; +const common_1 = require("@nestjs/common"); +const chain_adapter_manager_service_1 = require("./chain-adapter-manager.service"); +const evm_service_1 = require("./chain-adapters/evm.service"); +const utxo_service_1 = require("./chain-adapters/utxo.service"); +const cosmos_sdk_service_1 = require("./chain-adapters/cosmos-sdk.service"); +const solana_service_1 = require("./chain-adapters/solana.service"); +let ChainAdapterInitService = ChainAdapterInitService_1 = class ChainAdapterInitService { + constructor(chainAdapterManagerService, evmChainAdapterService, utxoChainAdapterService, cosmosSdkChainAdapterService, solanaChainAdapterService) { + this.chainAdapterManagerService = chainAdapterManagerService; + this.evmChainAdapterService = evmChainAdapterService; + this.utxoChainAdapterService = utxoChainAdapterService; + this.cosmosSdkChainAdapterService = cosmosSdkChainAdapterService; + this.solanaChainAdapterService = solanaChainAdapterService; + this.logger = new common_1.Logger(ChainAdapterInitService_1.name); + } + initializeChainAdapters() { + this.logger.log('Initializing chain adapters...'); + try { + this.evmChainAdapterService.initializeEvmChainAdapters(); + this.utxoChainAdapterService.initializeUtxoChainAdapters(); + this.cosmosSdkChainAdapterService.initializeCosmosSdkChainAdapters(); + this.solanaChainAdapterService.initializeSolanaChainAdapter(); + this.logger.log('All chain adapters initialized successfully'); + } + catch (error) { + this.logger.error('Failed to initialize chain adapters:', error); + throw error; + } + } + getChainAdapterManager() { + return this.chainAdapterManagerService.getChainAdapterManager(); + } +}; +exports.ChainAdapterInitService = ChainAdapterInitService; +exports.ChainAdapterInitService = ChainAdapterInitService = ChainAdapterInitService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [chain_adapter_manager_service_1.ChainAdapterManagerService, + evm_service_1.EvmChainAdapterService, + utxo_service_1.UtxoChainAdapterService, + cosmos_sdk_service_1.CosmosSdkChainAdapterService, + solana_service_1.SolanaChainAdapterService]) +], ChainAdapterInitService); diff --git a/apps/swap-service/src/lib/chain-adapter-init.service.ts b/apps/swap-service/src/lib/chain-adapter-init.service.ts index 6c2f9b6..36dffa9 100644 --- a/apps/swap-service/src/lib/chain-adapter-init.service.ts +++ b/apps/swap-service/src/lib/chain-adapter-init.service.ts @@ -17,17 +17,17 @@ export class ChainAdapterInitService { private solanaChainAdapterService: SolanaChainAdapterService, ) {} - async initializeChainAdapters() { + initializeChainAdapters() { this.logger.log('Initializing chain adapters...'); try { - await this.evmChainAdapterService.initializeEvmChainAdapters(); - - await this.utxoChainAdapterService.initializeUtxoChainAdapters(); - - await this.cosmosSdkChainAdapterService.initializeCosmosSdkChainAdapters(); - - await this.solanaChainAdapterService.initializeSolanaChainAdapter(); + this.evmChainAdapterService.initializeEvmChainAdapters(); + + this.utxoChainAdapterService.initializeUtxoChainAdapters(); + + this.cosmosSdkChainAdapterService.initializeCosmosSdkChainAdapters(); + + this.solanaChainAdapterService.initializeSolanaChainAdapter(); this.logger.log('All chain adapters initialized successfully'); } catch (error) { diff --git a/apps/swap-service/src/lib/chain-adapter-manager.service.js b/apps/swap-service/src/lib/chain-adapter-manager.service.js new file mode 100644 index 0000000..2e407df --- /dev/null +++ b/apps/swap-service/src/lib/chain-adapter-manager.service.js @@ -0,0 +1,25 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ChainAdapterManagerService = void 0; +const common_1 = require("@nestjs/common"); +let ChainAdapterManagerService = class ChainAdapterManagerService { + constructor() { + this.chainAdapterManager = new Map(); + } + getChainAdapterManager() { + return this.chainAdapterManager; + } + setChainAdapterManager(manager) { + this.chainAdapterManager = manager; + } +}; +exports.ChainAdapterManagerService = ChainAdapterManagerService; +exports.ChainAdapterManagerService = ChainAdapterManagerService = __decorate([ + (0, common_1.Injectable)() +], ChainAdapterManagerService); diff --git a/apps/swap-service/src/lib/chain-adapters/cosmos-sdk.service.js b/apps/swap-service/src/lib/chain-adapters/cosmos-sdk.service.js new file mode 100644 index 0000000..228c61c --- /dev/null +++ b/apps/swap-service/src/lib/chain-adapters/cosmos-sdk.service.js @@ -0,0 +1,132 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var CosmosSdkChainAdapterService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CosmosSdkChainAdapterService = void 0; +const common_1 = require("@nestjs/common"); +const chain_adapter_manager_service_1 = require("../chain-adapter-manager.service"); +const unchained = __importStar(require("@shapeshiftoss/unchained-client")); +const chain_adapters_1 = require("@shapeshiftoss/chain-adapters"); +const caip_1 = require("@shapeshiftoss/caip"); +const chain_adapters_2 = require("@shapeshiftoss/chain-adapters"); +let CosmosSdkChainAdapterService = CosmosSdkChainAdapterService_1 = class CosmosSdkChainAdapterService { + constructor(chainAdapterManagerService) { + this.chainAdapterManagerService = chainAdapterManagerService; + this.logger = new common_1.Logger(CosmosSdkChainAdapterService_1.name); + } + initializeCosmosSdkChainAdapters() { + this.logger.log('Initializing Cosmos SDK chain adapters...'); + const chainAdapterManager = this.chainAdapterManagerService.getChainAdapterManager(); + try { + this.initializeCosmosAdapter(chainAdapterManager); + this.initializeThorchainAdapter(chainAdapterManager); + this.initializeMayachainAdapter(chainAdapterManager); + this.logger.log('All Cosmos SDK chain adapters initialized successfully'); + } + catch (error) { + this.logger.error('Failed to initialize Cosmos SDK chain adapters:', error); + throw error; + } + } + initializeCosmosAdapter(chainAdapterManager) { + const cosmosHttp = new unchained.cosmos.V1Api(new unchained.cosmos.Configuration({ + basePath: process.env.VITE_UNCHAINED_COSMOS_HTTP_URL, + })); + const cosmosWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_COSMOS_WS_URL); + const cosmosAdapter = new chain_adapters_1.cosmos.ChainAdapter({ + providers: { http: cosmosHttp, ws: cosmosWs }, + midgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, + coinName: 'Cosmos', + }); + chainAdapterManager.set(caip_1.cosmosChainId, cosmosAdapter); + this.logger.log('Cosmos adapter initialized'); + } + initializeThorchainAdapter(chainAdapterManager) { + const http = new unchained.thorchain.V1Api(new unchained.thorchain.Configuration({ + basePath: process.env.VITE_UNCHAINED_THORCHAIN_HTTP_URL, + })); + const httpV1 = new unchained.thorchainV1.V1Api(new unchained.thorchainV1.Configuration({ + basePath: process.env.VITE_UNCHAINED_THORCHAIN_V1_HTTP_URL, + })); + const ws = new unchained.ws.Client(process.env.VITE_UNCHAINED_THORCHAIN_WS_URL); + const thorchainAdapter = new chain_adapters_1.thorchain.ChainAdapter({ + providers: { http, ws }, + thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, + mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, + coinName: 'THOR', + httpV1, + }); + chainAdapterManager.set(caip_1.thorchainChainId, thorchainAdapter); + this.logger.log('Thorchain adapter initialized'); + } + initializeMayachainAdapter(chainAdapterManager) { + const mayachainHttp = new unchained.mayachain.V1Api(new unchained.mayachain.Configuration({ + basePath: process.env.VITE_UNCHAINED_MAYACHAIN_HTTP_URL, + })); + const mayachainWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_MAYACHAIN_WS_URL); + const mayachainAdapter = new chain_adapters_1.mayachain.ChainAdapter({ + providers: { http: mayachainHttp, ws: mayachainWs }, + midgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, + coinName: 'MAYA', + }); + chainAdapterManager.set(caip_1.mayachainChainId, mayachainAdapter); + this.logger.log('Mayachain adapter initialized'); + } + assertGetCosmosSdkChainAdapter(chainId) { + if (!chain_adapters_2.cosmosSdkChainIds.includes(chainId)) { + throw new Error(`Chain ${chainId} is not a Cosmos SDK chain`); + } + const chainAdapterManager = this.chainAdapterManagerService.getChainAdapterManager(); + const adapter = chainAdapterManager.get(chainId); + if (!adapter) { + throw new Error(`Cosmos SDK chain adapter not found for chain ${chainId}`); + } + return adapter; + } +}; +exports.CosmosSdkChainAdapterService = CosmosSdkChainAdapterService; +exports.CosmosSdkChainAdapterService = CosmosSdkChainAdapterService = CosmosSdkChainAdapterService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [chain_adapter_manager_service_1.ChainAdapterManagerService]) +], CosmosSdkChainAdapterService); diff --git a/apps/swap-service/src/lib/chain-adapters/cosmos-sdk.service.ts b/apps/swap-service/src/lib/chain-adapters/cosmos-sdk.service.ts index 6c9c1cd..603b6e9 100644 --- a/apps/swap-service/src/lib/chain-adapters/cosmos-sdk.service.ts +++ b/apps/swap-service/src/lib/chain-adapters/cosmos-sdk.service.ts @@ -2,12 +2,15 @@ import { Injectable, Logger } from '@nestjs/common'; import { ChainAdapterManagerService } from '../chain-adapter-manager.service'; import * as unchained from '@shapeshiftoss/unchained-client'; import { cosmos, thorchain, mayachain } from '@shapeshiftoss/chain-adapters'; -import { +import { cosmosChainId, thorchainChainId, mayachainChainId, } from '@shapeshiftoss/caip'; -import { cosmosSdkChainIds, type CosmosSdkChainAdapter } from '@shapeshiftoss/chain-adapters'; +import { + cosmosSdkChainIds, + type CosmosSdkChainAdapter, +} from '@shapeshiftoss/chain-adapters'; import type { ChainId } from '@shapeshiftoss/caip'; import { CosmosSdkChainId } from '@shapeshiftoss/types'; @@ -17,24 +20,28 @@ export class CosmosSdkChainAdapterService { constructor(private chainAdapterManagerService: ChainAdapterManagerService) {} - async initializeCosmosSdkChainAdapters() { + initializeCosmosSdkChainAdapters() { this.logger.log('Initializing Cosmos SDK chain adapters...'); - - const chainAdapterManager = this.chainAdapterManagerService.getChainAdapterManager(); + + const chainAdapterManager = + this.chainAdapterManagerService.getChainAdapterManager(); try { - await this.initializeCosmosAdapter(chainAdapterManager); - await this.initializeThorchainAdapter(chainAdapterManager); - await this.initializeMayachainAdapter(chainAdapterManager); + this.initializeCosmosAdapter(chainAdapterManager); + this.initializeThorchainAdapter(chainAdapterManager); + this.initializeMayachainAdapter(chainAdapterManager); this.logger.log('All Cosmos SDK chain adapters initialized successfully'); } catch (error) { - this.logger.error('Failed to initialize Cosmos SDK chain adapters:', error); + this.logger.error( + 'Failed to initialize Cosmos SDK chain adapters:', + error, + ); throw error; } } - private async initializeCosmosAdapter(chainAdapterManager: Map) { + private initializeCosmosAdapter(chainAdapterManager: Map) { const cosmosHttp = new unchained.cosmos.V1Api( new unchained.cosmos.Configuration({ basePath: process.env.VITE_UNCHAINED_COSMOS_HTTP_URL, @@ -55,22 +62,22 @@ export class CosmosSdkChainAdapterService { this.logger.log('Cosmos adapter initialized'); } - private async initializeThorchainAdapter(chainAdapterManager: Map) { + private initializeThorchainAdapter(chainAdapterManager: Map) { const http = new unchained.thorchain.V1Api( new unchained.thorchain.Configuration({ - basePath: process.env.VITE_UNCHAINED_THORCHAIN_HTTP_URL, + basePath: process.env.VITE_UNCHAINED_THORCHAIN_HTTP_URL, }), - ) + ); const httpV1 = new unchained.thorchainV1.V1Api( new unchained.thorchainV1.Configuration({ basePath: process.env.VITE_UNCHAINED_THORCHAIN_V1_HTTP_URL, }), - ) + ); const ws = new unchained.ws.Client( process.env.VITE_UNCHAINED_THORCHAIN_WS_URL, - ) + ); const thorchainAdapter = new thorchain.ChainAdapter({ providers: { http, ws }, @@ -84,7 +91,7 @@ export class CosmosSdkChainAdapterService { this.logger.log('Thorchain adapter initialized'); } - private async initializeMayachainAdapter(chainAdapterManager: Map) { + private initializeMayachainAdapter(chainAdapterManager: Map) { const mayachainHttp = new unchained.mayachain.V1Api( new unchained.mayachain.Configuration({ basePath: process.env.VITE_UNCHAINED_MAYACHAIN_HTTP_URL, @@ -110,11 +117,14 @@ export class CosmosSdkChainAdapterService { throw new Error(`Chain ${chainId} is not a Cosmos SDK chain`); } - const chainAdapterManager = this.chainAdapterManagerService.getChainAdapterManager(); + const chainAdapterManager = + this.chainAdapterManagerService.getChainAdapterManager(); const adapter = chainAdapterManager.get(chainId); if (!adapter) { - throw new Error(`Cosmos SDK chain adapter not found for chain ${chainId}`); + throw new Error( + `Cosmos SDK chain adapter not found for chain ${chainId}`, + ); } return adapter as CosmosSdkChainAdapter; diff --git a/apps/swap-service/src/lib/chain-adapters/evm.service.js b/apps/swap-service/src/lib/chain-adapters/evm.service.js new file mode 100644 index 0000000..cb4623a --- /dev/null +++ b/apps/swap-service/src/lib/chain-adapters/evm.service.js @@ -0,0 +1,220 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var EvmChainAdapterService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.EvmChainAdapterService = void 0; +const common_1 = require("@nestjs/common"); +const chain_adapter_manager_service_1 = require("../chain-adapter-manager.service"); +const unchained = __importStar(require("@shapeshiftoss/unchained-client")); +const chain_adapters_1 = require("@shapeshiftoss/chain-adapters"); +const caip_1 = require("@shapeshiftoss/caip"); +const chain_adapters_2 = require("@shapeshiftoss/chain-adapters"); +let EvmChainAdapterService = EvmChainAdapterService_1 = class EvmChainAdapterService { + constructor(chainAdapterManagerService) { + this.chainAdapterManagerService = chainAdapterManagerService; + this.logger = new common_1.Logger(EvmChainAdapterService_1.name); + } + async initializeEvmChainAdapters() { + this.logger.log('Initializing EVM chain adapters...'); + const chainAdapterManager = this.chainAdapterManagerService.getChainAdapterManager(); + try { + await this.initializeEthereumAdapter(chainAdapterManager); + await this.initializeAvalancheAdapter(chainAdapterManager); + await this.initializeOptimismAdapter(chainAdapterManager); + await this.initializeBscAdapter(chainAdapterManager); + await this.initializePolygonAdapter(chainAdapterManager); + await this.initializeGnosisAdapter(chainAdapterManager); + await this.initializeArbitrumAdapter(chainAdapterManager); + await this.initializeArbitrumNovaAdapter(chainAdapterManager); + await this.initializeBaseAdapter(chainAdapterManager); + this.logger.log('All EVM chain adapters initialized successfully'); + } + catch (error) { + this.logger.error('Failed to initialize EVM chain adapters:', error); + throw error; + } + } + async initializeEthereumAdapter(chainAdapterManager) { + const ethereumHttp = new unchained.ethereum.V1Api(new unchained.ethereum.Configuration({ + basePath: process.env.VITE_UNCHAINED_ETHEREUM_HTTP_URL, + })); + const ethereumWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_ETHEREUM_WS_URL); + const ethereumAdapter = new chain_adapters_1.ethereum.ChainAdapter({ + providers: { http: ethereumHttp, ws: ethereumWs }, + rpcUrl: process.env.VITE_ETHEREUM_NODE_URL, + thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, + mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, + }); + chainAdapterManager.set(caip_1.ethChainId, ethereumAdapter); + this.logger.log('Ethereum adapter initialized'); + } + async initializeAvalancheAdapter(chainAdapterManager) { + const avalancheHttp = new unchained.ethereum.V1Api(new unchained.ethereum.Configuration({ + basePath: process.env.VITE_UNCHAINED_AVALANCHE_HTTP_URL, + })); + const avalancheWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_AVALANCHE_WS_URL); + const avalancheAdapter = new chain_adapters_1.ethereum.ChainAdapter({ + providers: { http: avalancheHttp, ws: avalancheWs }, + rpcUrl: process.env.VITE_AVALANCHE_NODE_URL, + thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, + mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, + }); + chainAdapterManager.set(caip_1.avalancheChainId, avalancheAdapter); + this.logger.log('Avalanche adapter initialized'); + } + async initializeOptimismAdapter(chainAdapterManager) { + const optimismHttp = new unchained.ethereum.V1Api(new unchained.ethereum.Configuration({ + basePath: process.env.VITE_UNCHAINED_OPTIMISM_HTTP_URL, + })); + const optimismWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_OPTIMISM_WS_URL); + const optimismAdapter = new chain_adapters_1.ethereum.ChainAdapter({ + providers: { http: optimismHttp, ws: optimismWs }, + rpcUrl: process.env.VITE_OPTIMISM_NODE_URL, + thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, + mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, + }); + chainAdapterManager.set(caip_1.optimismChainId, optimismAdapter); + this.logger.log('Optimism adapter initialized'); + } + async initializeBscAdapter(chainAdapterManager) { + const bscHttp = new unchained.ethereum.V1Api(new unchained.ethereum.Configuration({ + basePath: process.env.VITE_UNCHAINED_BNBSMARTCHAIN_HTTP_URL, + })); + const bscWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_BNBSMARTCHAIN_WS_URL); + const bscAdapter = new chain_adapters_1.ethereum.ChainAdapter({ + providers: { http: bscHttp, ws: bscWs }, + rpcUrl: process.env.VITE_BNBSMARTCHAIN_NODE_URL, + thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, + mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, + }); + chainAdapterManager.set(caip_1.bscChainId, bscAdapter); + this.logger.log('BNB Smart Chain adapter initialized'); + } + async initializePolygonAdapter(chainAdapterManager) { + const polygonHttp = new unchained.ethereum.V1Api(new unchained.ethereum.Configuration({ + basePath: process.env.VITE_UNCHAINED_POLYGON_HTTP_URL, + })); + const polygonWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_POLYGON_WS_URL); + const polygonAdapter = new chain_adapters_1.ethereum.ChainAdapter({ + providers: { http: polygonHttp, ws: polygonWs }, + rpcUrl: process.env.VITE_POLYGON_NODE_URL, + thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, + mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, + }); + chainAdapterManager.set(caip_1.polygonChainId, polygonAdapter); + this.logger.log('Polygon adapter initialized'); + } + async initializeGnosisAdapter(chainAdapterManager) { + const gnosisHttp = new unchained.ethereum.V1Api(new unchained.ethereum.Configuration({ + basePath: process.env.VITE_UNCHAINED_GNOSIS_HTTP_URL, + })); + const gnosisWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_GNOSIS_WS_URL); + const gnosisAdapter = new chain_adapters_1.ethereum.ChainAdapter({ + providers: { http: gnosisHttp, ws: gnosisWs }, + rpcUrl: process.env.VITE_GNOSIS_NODE_URL, + thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, + mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, + }); + chainAdapterManager.set(caip_1.gnosisChainId, gnosisAdapter); + this.logger.log('Gnosis adapter initialized'); + } + async initializeArbitrumAdapter(chainAdapterManager) { + const arbitrumHttp = new unchained.ethereum.V1Api(new unchained.ethereum.Configuration({ + basePath: process.env.VITE_UNCHAINED_ARBITRUM_HTTP_URL, + })); + const arbitrumWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_ARBITRUM_WS_URL); + const arbitrumAdapter = new chain_adapters_1.ethereum.ChainAdapter({ + providers: { http: arbitrumHttp, ws: arbitrumWs }, + rpcUrl: process.env.VITE_ARBITRUM_NODE_URL, + thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, + mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, + }); + chainAdapterManager.set(caip_1.arbitrumChainId, arbitrumAdapter); + this.logger.log('Arbitrum adapter initialized'); + } + async initializeArbitrumNovaAdapter(chainAdapterManager) { + const arbitrumNovaHttp = new unchained.ethereum.V1Api(new unchained.ethereum.Configuration({ + basePath: process.env.VITE_UNCHAINED_ARBITRUM_NOVA_HTTP_URL, + })); + const arbitrumNovaWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_ARBITRUM_NOVA_WS_URL); + const arbitrumNovaAdapter = new chain_adapters_1.ethereum.ChainAdapter({ + providers: { http: arbitrumNovaHttp, ws: arbitrumNovaWs }, + rpcUrl: process.env.VITE_ARBITRUM_NOVA_NODE_URL, + thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, + mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, + }); + chainAdapterManager.set(caip_1.arbitrumNovaChainId, arbitrumNovaAdapter); + this.logger.log('Arbitrum Nova adapter initialized'); + } + async initializeBaseAdapter(chainAdapterManager) { + const baseHttp = new unchained.ethereum.V1Api(new unchained.ethereum.Configuration({ + basePath: process.env.VITE_UNCHAINED_BASE_HTTP_URL, + })); + const baseWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_BASE_WS_URL); + const baseAdapter = new chain_adapters_1.ethereum.ChainAdapter({ + providers: { http: baseHttp, ws: baseWs }, + rpcUrl: process.env.VITE_BASE_NODE_URL, + thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, + mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, + }); + chainAdapterManager.set(caip_1.baseChainId, baseAdapter); + this.logger.log('Base adapter initialized'); + } + isEvmChainAdapter(chainAdapter) { + return chain_adapters_2.evmChainIds.includes(chainAdapter.getChainId()); + } + assertGetEvmChainAdapter(chainId) { + const chainAdapterManager = this.chainAdapterManagerService.getChainAdapterManager(); + const adapter = chainAdapterManager.get(chainId); + if (!this.isEvmChainAdapter(adapter)) { + throw Error('invalid chain adapter'); + } + return adapter; + } +}; +exports.EvmChainAdapterService = EvmChainAdapterService; +exports.EvmChainAdapterService = EvmChainAdapterService = EvmChainAdapterService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [chain_adapter_manager_service_1.ChainAdapterManagerService]) +], EvmChainAdapterService); diff --git a/apps/swap-service/src/lib/chain-adapters/evm.service.ts b/apps/swap-service/src/lib/chain-adapters/evm.service.ts index aa48253..d419ac2 100644 --- a/apps/swap-service/src/lib/chain-adapters/evm.service.ts +++ b/apps/swap-service/src/lib/chain-adapters/evm.service.ts @@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { ChainAdapterManagerService } from '../chain-adapter-manager.service'; import * as unchained from '@shapeshiftoss/unchained-client'; import { ethereum } from '@shapeshiftoss/chain-adapters'; -import { +import { ethChainId, avalancheChainId, optimismChainId, @@ -13,7 +13,10 @@ import { arbitrumNovaChainId, baseChainId, } from '@shapeshiftoss/caip'; -import { evmChainIds, type EvmChainAdapter } from '@shapeshiftoss/chain-adapters'; +import { + evmChainIds, + type EvmChainAdapter, +} from '@shapeshiftoss/chain-adapters'; import type { ChainId } from '@shapeshiftoss/caip'; import { EvmChainId } from '@shapeshiftoss/types'; @@ -23,29 +26,30 @@ export class EvmChainAdapterService { constructor(private chainAdapterManagerService: ChainAdapterManagerService) {} - async initializeEvmChainAdapters() { + initializeEvmChainAdapters() { this.logger.log('Initializing EVM chain adapters...'); - - const chainAdapterManager = this.chainAdapterManagerService.getChainAdapterManager(); + + const chainAdapterManager = + this.chainAdapterManagerService.getChainAdapterManager(); try { - await this.initializeEthereumAdapter(chainAdapterManager); - - await this.initializeAvalancheAdapter(chainAdapterManager); - - await this.initializeOptimismAdapter(chainAdapterManager); - - await this.initializeBscAdapter(chainAdapterManager); - - await this.initializePolygonAdapter(chainAdapterManager); - - await this.initializeGnosisAdapter(chainAdapterManager); - - await this.initializeArbitrumAdapter(chainAdapterManager); - - await this.initializeArbitrumNovaAdapter(chainAdapterManager); - - await this.initializeBaseAdapter(chainAdapterManager); + this.initializeEthereumAdapter(chainAdapterManager); + + this.initializeAvalancheAdapter(chainAdapterManager); + + this.initializeOptimismAdapter(chainAdapterManager); + + this.initializeBscAdapter(chainAdapterManager); + + this.initializePolygonAdapter(chainAdapterManager); + + this.initializeGnosisAdapter(chainAdapterManager); + + this.initializeArbitrumAdapter(chainAdapterManager); + + this.initializeArbitrumNovaAdapter(chainAdapterManager); + + this.initializeBaseAdapter(chainAdapterManager); this.logger.log('All EVM chain adapters initialized successfully'); } catch (error) { @@ -54,7 +58,7 @@ export class EvmChainAdapterService { } } - private async initializeEthereumAdapter(chainAdapterManager: Map) { + private initializeEthereumAdapter(chainAdapterManager: Map) { const ethereumHttp = new unchained.ethereum.V1Api( new unchained.ethereum.Configuration({ basePath: process.env.VITE_UNCHAINED_ETHEREUM_HTTP_URL, @@ -76,7 +80,7 @@ export class EvmChainAdapterService { this.logger.log('Ethereum adapter initialized'); } - private async initializeAvalancheAdapter(chainAdapterManager: Map) { + private initializeAvalancheAdapter(chainAdapterManager: Map) { const avalancheHttp = new unchained.ethereum.V1Api( new unchained.ethereum.Configuration({ basePath: process.env.VITE_UNCHAINED_AVALANCHE_HTTP_URL, @@ -98,7 +102,7 @@ export class EvmChainAdapterService { this.logger.log('Avalanche adapter initialized'); } - private async initializeOptimismAdapter(chainAdapterManager: Map) { + private initializeOptimismAdapter(chainAdapterManager: Map) { const optimismHttp = new unchained.ethereum.V1Api( new unchained.ethereum.Configuration({ basePath: process.env.VITE_UNCHAINED_OPTIMISM_HTTP_URL, @@ -120,7 +124,7 @@ export class EvmChainAdapterService { this.logger.log('Optimism adapter initialized'); } - private async initializeBscAdapter(chainAdapterManager: Map) { + private initializeBscAdapter(chainAdapterManager: Map) { const bscHttp = new unchained.ethereum.V1Api( new unchained.ethereum.Configuration({ basePath: process.env.VITE_UNCHAINED_BNBSMARTCHAIN_HTTP_URL, @@ -142,7 +146,7 @@ export class EvmChainAdapterService { this.logger.log('BNB Smart Chain adapter initialized'); } - private async initializePolygonAdapter(chainAdapterManager: Map) { + private initializePolygonAdapter(chainAdapterManager: Map) { const polygonHttp = new unchained.ethereum.V1Api( new unchained.ethereum.Configuration({ basePath: process.env.VITE_UNCHAINED_POLYGON_HTTP_URL, @@ -164,7 +168,7 @@ export class EvmChainAdapterService { this.logger.log('Polygon adapter initialized'); } - private async initializeGnosisAdapter(chainAdapterManager: Map) { + private initializeGnosisAdapter(chainAdapterManager: Map) { const gnosisHttp = new unchained.ethereum.V1Api( new unchained.ethereum.Configuration({ basePath: process.env.VITE_UNCHAINED_GNOSIS_HTTP_URL, @@ -186,7 +190,7 @@ export class EvmChainAdapterService { this.logger.log('Gnosis adapter initialized'); } - private async initializeArbitrumAdapter(chainAdapterManager: Map) { + private initializeArbitrumAdapter(chainAdapterManager: Map) { const arbitrumHttp = new unchained.ethereum.V1Api( new unchained.ethereum.Configuration({ basePath: process.env.VITE_UNCHAINED_ARBITRUM_HTTP_URL, @@ -208,7 +212,7 @@ export class EvmChainAdapterService { this.logger.log('Arbitrum adapter initialized'); } - private async initializeArbitrumNovaAdapter(chainAdapterManager: Map) { + private initializeArbitrumNovaAdapter(chainAdapterManager: Map) { const arbitrumNovaHttp = new unchained.ethereum.V1Api( new unchained.ethereum.Configuration({ basePath: process.env.VITE_UNCHAINED_ARBITRUM_NOVA_HTTP_URL, @@ -230,7 +234,7 @@ export class EvmChainAdapterService { this.logger.log('Arbitrum Nova adapter initialized'); } - private async initializeBaseAdapter(chainAdapterManager: Map) { + private initializeBaseAdapter(chainAdapterManager: Map) { const baseHttp = new unchained.ethereum.V1Api( new unchained.ethereum.Configuration({ basePath: process.env.VITE_UNCHAINED_BASE_HTTP_URL, @@ -253,11 +257,14 @@ export class EvmChainAdapterService { } isEvmChainAdapter(chainAdapter: unknown): chainAdapter is EvmChainAdapter { - return evmChainIds.includes((chainAdapter as EvmChainAdapter).getChainId() as EvmChainId); + return evmChainIds.includes( + (chainAdapter as EvmChainAdapter).getChainId() as EvmChainId, + ); } assertGetEvmChainAdapter(chainId: ChainId): EvmChainAdapter { - const chainAdapterManager = this.chainAdapterManagerService.getChainAdapterManager(); + const chainAdapterManager = + this.chainAdapterManagerService.getChainAdapterManager(); const adapter = chainAdapterManager.get(chainId); if (!this.isEvmChainAdapter(adapter)) { diff --git a/apps/swap-service/src/lib/chain-adapters/solana.service.js b/apps/swap-service/src/lib/chain-adapters/solana.service.js new file mode 100644 index 0000000..c213ec7 --- /dev/null +++ b/apps/swap-service/src/lib/chain-adapters/solana.service.js @@ -0,0 +1,94 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var SolanaChainAdapterService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SolanaChainAdapterService = void 0; +const common_1 = require("@nestjs/common"); +const chain_adapter_manager_service_1 = require("../chain-adapter-manager.service"); +const unchained = __importStar(require("@shapeshiftoss/unchained-client")); +const chain_adapters_1 = require("@shapeshiftoss/chain-adapters"); +const caip_1 = require("@shapeshiftoss/caip"); +let SolanaChainAdapterService = SolanaChainAdapterService_1 = class SolanaChainAdapterService { + constructor(chainAdapterManagerService) { + this.chainAdapterManagerService = chainAdapterManagerService; + this.logger = new common_1.Logger(SolanaChainAdapterService_1.name); + } + initializeSolanaChainAdapter() { + this.logger.log('Initializing Solana chain adapter...'); + const chainAdapterManager = this.chainAdapterManagerService.getChainAdapterManager(); + try { + this.initializeSolanaAdapter(chainAdapterManager); + this.logger.log('Solana chain adapter initialized successfully'); + } + catch (error) { + this.logger.error('Failed to initialize Solana chain adapter:', error); + throw error; + } + } + initializeSolanaAdapter(chainAdapterManager) { + const solanaHttp = new unchained.solana.V1Api(new unchained.solana.Configuration({ + basePath: process.env.VITE_UNCHAINED_SOLANA_HTTP_URL, + })); + const solanaWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_SOLANA_WS_URL); + const solanaAdapter = new chain_adapters_1.solana.ChainAdapter({ + providers: { http: solanaHttp, ws: solanaWs }, + rpcUrl: process.env.VITE_SOLANA_NODE_URL, + }); + chainAdapterManager.set(caip_1.solanaChainId, solanaAdapter); + this.logger.log('Solana adapter initialized'); + } + assertGetSolanaChainAdapter(chainId) { + const chainAdapterManager = this.chainAdapterManagerService.getChainAdapterManager(); + const adapter = chainAdapterManager.get(chainId); + if (!adapter) { + throw new Error(`Solana chain adapter not found for chain ${chainId}`); + } + return adapter; + } +}; +exports.SolanaChainAdapterService = SolanaChainAdapterService; +exports.SolanaChainAdapterService = SolanaChainAdapterService = SolanaChainAdapterService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [chain_adapter_manager_service_1.ChainAdapterManagerService]) +], SolanaChainAdapterService); diff --git a/apps/swap-service/src/lib/chain-adapters/solana.service.ts b/apps/swap-service/src/lib/chain-adapters/solana.service.ts index fa49d9a..27d33e7 100644 --- a/apps/swap-service/src/lib/chain-adapters/solana.service.ts +++ b/apps/swap-service/src/lib/chain-adapters/solana.service.ts @@ -11,13 +11,14 @@ export class SolanaChainAdapterService { constructor(private chainAdapterManagerService: ChainAdapterManagerService) {} - async initializeSolanaChainAdapter() { + initializeSolanaChainAdapter() { this.logger.log('Initializing Solana chain adapter...'); - - const chainAdapterManager = this.chainAdapterManagerService.getChainAdapterManager(); + + const chainAdapterManager = + this.chainAdapterManagerService.getChainAdapterManager(); try { - await this.initializeSolanaAdapter(chainAdapterManager); + this.initializeSolanaAdapter(chainAdapterManager); this.logger.log('Solana chain adapter initialized successfully'); } catch (error) { this.logger.error('Failed to initialize Solana chain adapter:', error); @@ -25,7 +26,7 @@ export class SolanaChainAdapterService { } } - private async initializeSolanaAdapter(chainAdapterManager: Map) { + private initializeSolanaAdapter(chainAdapterManager: Map) { const solanaHttp = new unchained.solana.V1Api( new unchained.solana.Configuration({ basePath: process.env.VITE_UNCHAINED_SOLANA_HTTP_URL, @@ -46,7 +47,8 @@ export class SolanaChainAdapterService { } assertGetSolanaChainAdapter(chainId: ChainId): solana.ChainAdapter { - const chainAdapterManager = this.chainAdapterManagerService.getChainAdapterManager(); + const chainAdapterManager = + this.chainAdapterManagerService.getChainAdapterManager(); const adapter = chainAdapterManager.get(chainId); if (!adapter) { diff --git a/apps/swap-service/src/lib/chain-adapters/utxo.service.js b/apps/swap-service/src/lib/chain-adapters/utxo.service.js new file mode 100644 index 0000000..acfadaf --- /dev/null +++ b/apps/swap-service/src/lib/chain-adapters/utxo.service.js @@ -0,0 +1,145 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var UtxoChainAdapterService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.UtxoChainAdapterService = void 0; +const common_1 = require("@nestjs/common"); +const chain_adapter_manager_service_1 = require("../chain-adapter-manager.service"); +const unchained = __importStar(require("@shapeshiftoss/unchained-client")); +const chain_adapters_1 = require("@shapeshiftoss/chain-adapters"); +const caip_1 = require("@shapeshiftoss/caip"); +const chain_adapters_2 = require("@shapeshiftoss/chain-adapters"); +let UtxoChainAdapterService = UtxoChainAdapterService_1 = class UtxoChainAdapterService { + constructor(chainAdapterManagerService) { + this.chainAdapterManagerService = chainAdapterManagerService; + this.logger = new common_1.Logger(UtxoChainAdapterService_1.name); + } + async initializeUtxoChainAdapters() { + this.logger.log('Initializing UTXO chain adapters...'); + const chainAdapterManager = this.chainAdapterManagerService.getChainAdapterManager(); + try { + await this.initializeBitcoinAdapter(chainAdapterManager); + await this.initializeBitcoinCashAdapter(chainAdapterManager); + await this.initializeDogecoinAdapter(chainAdapterManager); + await this.initializeLitecoinAdapter(chainAdapterManager); + this.logger.log('All UTXO chain adapters initialized successfully'); + } + catch (error) { + this.logger.error('Failed to initialize UTXO chain adapters:', error); + throw error; + } + } + async initializeBitcoinAdapter(chainAdapterManager) { + const bitcoinHttp = new unchained.bitcoin.V1Api(new unchained.bitcoin.Configuration({ + basePath: process.env.VITE_UNCHAINED_BITCOIN_HTTP_URL, + })); + const bitcoinWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_BITCOIN_WS_URL); + const bitcoinAdapter = new chain_adapters_1.bitcoin.ChainAdapter({ + providers: { http: bitcoinHttp, ws: bitcoinWs }, + coinName: 'Bitcoin', + thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, + mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, + }); + chainAdapterManager.set(caip_1.btcChainId, bitcoinAdapter); + this.logger.log('Bitcoin adapter initialized'); + } + async initializeBitcoinCashAdapter(chainAdapterManager) { + const bchHttp = new unchained.bitcoin.V1Api(new unchained.bitcoin.Configuration({ + basePath: process.env.VITE_UNCHAINED_BITCOINCASH_HTTP_URL, + })); + const bchWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_BITCOINCASH_WS_URL); + const bchAdapter = new chain_adapters_1.bitcoin.ChainAdapter({ + providers: { http: bchHttp, ws: bchWs }, + coinName: 'BitcoinCash', + thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, + mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, + }); + chainAdapterManager.set(caip_1.bchChainId, bchAdapter); + this.logger.log('Bitcoin Cash adapter initialized'); + } + async initializeDogecoinAdapter(chainAdapterManager) { + const dogeHttp = new unchained.bitcoin.V1Api(new unchained.bitcoin.Configuration({ + basePath: process.env.VITE_UNCHAINED_DOGECOIN_HTTP_URL, + })); + const dogeWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_DOGECOIN_WS_URL); + const dogeAdapter = new chain_adapters_1.bitcoin.ChainAdapter({ + providers: { http: dogeHttp, ws: dogeWs }, + coinName: 'Dogecoin', + thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, + mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, + }); + chainAdapterManager.set(caip_1.dogeChainId, dogeAdapter); + this.logger.log('Dogecoin adapter initialized'); + } + async initializeLitecoinAdapter(chainAdapterManager) { + const ltcHttp = new unchained.bitcoin.V1Api(new unchained.bitcoin.Configuration({ + basePath: process.env.VITE_UNCHAINED_LITECOIN_HTTP_URL, + })); + const ltcWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_LITECOIN_WS_URL); + const ltcAdapter = new chain_adapters_1.bitcoin.ChainAdapter({ + providers: { http: ltcHttp, ws: ltcWs }, + coinName: 'Litecoin', + thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, + mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, + }); + chainAdapterManager.set(caip_1.ltcChainId, ltcAdapter); + this.logger.log('Litecoin adapter initialized'); + } + assertGetUtxoChainAdapter(chainId) { + if (!chain_adapters_2.utxoChainIds.includes(chainId)) { + throw new Error(`Chain ${chainId} is not a UTXO chain`); + } + const chainAdapterManager = this.chainAdapterManagerService.getChainAdapterManager(); + const adapter = chainAdapterManager.get(chainId); + if (!adapter) { + throw new Error(`UTXO chain adapter not found for chain ${chainId}`); + } + return adapter; + } +}; +exports.UtxoChainAdapterService = UtxoChainAdapterService; +exports.UtxoChainAdapterService = UtxoChainAdapterService = UtxoChainAdapterService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [chain_adapter_manager_service_1.ChainAdapterManagerService]) +], UtxoChainAdapterService); diff --git a/apps/swap-service/src/lib/chain-adapters/utxo.service.ts b/apps/swap-service/src/lib/chain-adapters/utxo.service.ts index b81f95d..9887980 100644 --- a/apps/swap-service/src/lib/chain-adapters/utxo.service.ts +++ b/apps/swap-service/src/lib/chain-adapters/utxo.service.ts @@ -2,13 +2,16 @@ import { Injectable, Logger } from '@nestjs/common'; import { ChainAdapterManagerService } from '../chain-adapter-manager.service'; import * as unchained from '@shapeshiftoss/unchained-client'; import { bitcoin } from '@shapeshiftoss/chain-adapters'; -import { +import { btcChainId, bchChainId, dogeChainId, ltcChainId, } from '@shapeshiftoss/caip'; -import { utxoChainIds, type UtxoChainAdapter } from '@shapeshiftoss/chain-adapters'; +import { + utxoChainIds, + type UtxoChainAdapter, +} from '@shapeshiftoss/chain-adapters'; import type { ChainId } from '@shapeshiftoss/caip'; import { UtxoChainId } from '@shapeshiftoss/types'; @@ -18,16 +21,17 @@ export class UtxoChainAdapterService { constructor(private chainAdapterManagerService: ChainAdapterManagerService) {} - async initializeUtxoChainAdapters() { + initializeUtxoChainAdapters() { this.logger.log('Initializing UTXO chain adapters...'); - - const chainAdapterManager = this.chainAdapterManagerService.getChainAdapterManager(); + + const chainAdapterManager = + this.chainAdapterManagerService.getChainAdapterManager(); try { - await this.initializeBitcoinAdapter(chainAdapterManager); - await this.initializeBitcoinCashAdapter(chainAdapterManager); - await this.initializeDogecoinAdapter(chainAdapterManager); - await this.initializeLitecoinAdapter(chainAdapterManager); + this.initializeBitcoinAdapter(chainAdapterManager); + this.initializeBitcoinCashAdapter(chainAdapterManager); + this.initializeDogecoinAdapter(chainAdapterManager); + this.initializeLitecoinAdapter(chainAdapterManager); this.logger.log('All UTXO chain adapters initialized successfully'); } catch (error) { @@ -36,7 +40,7 @@ export class UtxoChainAdapterService { } } - private async initializeBitcoinAdapter(chainAdapterManager: Map) { + private initializeBitcoinAdapter(chainAdapterManager: Map) { const bitcoinHttp = new unchained.bitcoin.V1Api( new unchained.bitcoin.Configuration({ basePath: process.env.VITE_UNCHAINED_BITCOIN_HTTP_URL, @@ -58,7 +62,7 @@ export class UtxoChainAdapterService { this.logger.log('Bitcoin adapter initialized'); } - private async initializeBitcoinCashAdapter(chainAdapterManager: Map) { + private initializeBitcoinCashAdapter(chainAdapterManager: Map) { const bchHttp = new unchained.bitcoin.V1Api( new unchained.bitcoin.Configuration({ basePath: process.env.VITE_UNCHAINED_BITCOINCASH_HTTP_URL, @@ -80,7 +84,7 @@ export class UtxoChainAdapterService { this.logger.log('Bitcoin Cash adapter initialized'); } - private async initializeDogecoinAdapter(chainAdapterManager: Map) { + private initializeDogecoinAdapter(chainAdapterManager: Map) { const dogeHttp = new unchained.bitcoin.V1Api( new unchained.bitcoin.Configuration({ basePath: process.env.VITE_UNCHAINED_DOGECOIN_HTTP_URL, @@ -102,7 +106,7 @@ export class UtxoChainAdapterService { this.logger.log('Dogecoin adapter initialized'); } - private async initializeLitecoinAdapter(chainAdapterManager: Map) { + private initializeLitecoinAdapter(chainAdapterManager: Map) { const ltcHttp = new unchained.bitcoin.V1Api( new unchained.bitcoin.Configuration({ basePath: process.env.VITE_UNCHAINED_LITECOIN_HTTP_URL, @@ -129,7 +133,8 @@ export class UtxoChainAdapterService { throw new Error(`Chain ${chainId} is not a UTXO chain`); } - const chainAdapterManager = this.chainAdapterManagerService.getChainAdapterManager(); + const chainAdapterManager = + this.chainAdapterManagerService.getChainAdapterManager(); const adapter = chainAdapterManager.get(chainId); if (!adapter) { diff --git a/apps/swap-service/src/main.js b/apps/swap-service/src/main.js new file mode 100644 index 0000000..45bdf39 --- /dev/null +++ b/apps/swap-service/src/main.js @@ -0,0 +1,26 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const core_1 = require("@nestjs/core"); +const app_module_1 = require("./app.module"); +const chain_adapter_init_service_1 = require("./lib/chain-adapter-init.service"); +async function bootstrap() { + const app = await core_1.NestFactory.create(app_module_1.AppModule); + // Initialize chain adapters + const chainAdapterInitService = app.get(chain_adapter_init_service_1.ChainAdapterInitService); + await chainAdapterInitService.initializeChainAdapters(); + // Enable CORS + app.enableCors({ + origin: process.env.ALLOWED_ORIGINS?.split(',') || [ + 'http://localhost:3000', + /\.shapeshift\.com$/, + ], + credentials: true, + }); + app.getHttpAdapter().get('/health', (_, res) => { + res.status(200).json({ status: 'ok' }); + }); + const port = process.env.PORT || 3000; + await app.listen(port); + console.log(`Swap service is running on: http://localhost:${port}`); +} +bootstrap(); diff --git a/apps/swap-service/src/polling/swap-polling.service.js b/apps/swap-service/src/polling/swap-polling.service.js new file mode 100644 index 0000000..de58f9e --- /dev/null +++ b/apps/swap-service/src/polling/swap-polling.service.js @@ -0,0 +1,69 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var SwapPollingService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SwapPollingService = void 0; +const common_1 = require("@nestjs/common"); +const schedule_1 = require("@nestjs/schedule"); +const swaps_service_1 = require("../swaps/swaps.service"); +const websocket_gateway_1 = require("../websocket/websocket.gateway"); +let SwapPollingService = SwapPollingService_1 = class SwapPollingService { + constructor(swapsService, websocketGateway) { + this.swapsService = swapsService; + this.websocketGateway = websocketGateway; + this.logger = new common_1.Logger(SwapPollingService_1.name); + } + async pollPendingSwaps() { + try { + this.logger.log('Starting to poll pending swaps...'); + const pendingSwaps = await this.swapsService.getPendingSwaps(); + if (pendingSwaps.length === 0) { + this.logger.log('No pending swaps found'); + return; + } + this.logger.log(`Found ${pendingSwaps.length} pending swaps to poll`); + for (const swap of pendingSwaps) { + try { + const statusUpdate = await this.swapsService.pollSwapStatus(swap.swapId); + if (statusUpdate.status !== swap.status) { + this.logger.log(`Status changed for swap ${swap.swapId}: ${swap.status} -> ${statusUpdate.status}`); + const updatedSwap = await this.swapsService.updateSwapStatus({ + swapId: swap.swapId, + status: statusUpdate.status, + sellTxHash: statusUpdate.sellTxHash, + buyTxHash: statusUpdate.buyTxHash, + statusMessage: statusUpdate.statusMessage, + }); + await this.websocketGateway.sendSwapUpdateToUser(swap.userId, updatedSwap); + } + } + catch (error) { + this.logger.error(`Failed to poll swap ${swap.swapId}:`, error); + } + } + } + catch (error) { + this.logger.error('Failed to poll pending swaps:', error); + } + } +}; +exports.SwapPollingService = SwapPollingService; +__decorate([ + (0, schedule_1.Cron)(schedule_1.CronExpression.EVERY_5_SECONDS), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", Promise) +], SwapPollingService.prototype, "pollPendingSwaps", null); +exports.SwapPollingService = SwapPollingService = SwapPollingService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [swaps_service_1.SwapsService, + websocket_gateway_1.WebsocketGateway]) +], SwapPollingService); diff --git a/apps/swap-service/src/polling/swap-polling.service.ts b/apps/swap-service/src/polling/swap-polling.service.ts index cde41e0..1c4d7e8 100644 --- a/apps/swap-service/src/polling/swap-polling.service.ts +++ b/apps/swap-service/src/polling/swap-polling.service.ts @@ -16,9 +16,9 @@ export class SwapPollingService { async pollPendingSwaps() { try { this.logger.log('Starting to poll pending swaps...'); - + const pendingSwaps = await this.swapsService.getPendingSwaps(); - + if (pendingSwaps.length === 0) { this.logger.log('No pending swaps found'); return; @@ -28,11 +28,15 @@ export class SwapPollingService { for (const swap of pendingSwaps) { try { - const statusUpdate = await this.swapsService.pollSwapStatus(swap.swapId); - + const statusUpdate = await this.swapsService.pollSwapStatus( + swap.swapId, + ); + if (statusUpdate.status !== swap.status) { - this.logger.log(`Status changed for swap ${swap.swapId}: ${swap.status} -> ${statusUpdate.status}`); - + this.logger.log( + `Status changed for swap ${swap.swapId}: ${swap.status} -> ${statusUpdate.status}`, + ); + const updatedSwap = await this.swapsService.updateSwapStatus({ swapId: swap.swapId, status: statusUpdate.status, @@ -41,7 +45,10 @@ export class SwapPollingService { statusMessage: statusUpdate.statusMessage, }); - await this.websocketGateway.sendSwapUpdateToUser(swap.userId, updatedSwap); + this.websocketGateway.sendSwapUpdateToUser( + swap.userId, + updatedSwap, + ); } } catch (error) { this.logger.error(`Failed to poll swap ${swap.swapId}:`, error); diff --git a/apps/swap-service/src/prisma/prisma.service.js b/apps/swap-service/src/prisma/prisma.service.js new file mode 100644 index 0000000..2b91c98 --- /dev/null +++ b/apps/swap-service/src/prisma/prisma.service.js @@ -0,0 +1,23 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PrismaService = void 0; +const common_1 = require("@nestjs/common"); +const client_1 = require("@prisma/client"); +let PrismaService = class PrismaService extends client_1.PrismaClient { + async onModuleInit() { + await this.$connect(); + } + async onModuleDestroy() { + await this.$disconnect(); + } +}; +exports.PrismaService = PrismaService; +exports.PrismaService = PrismaService = __decorate([ + (0, common_1.Injectable)() +], PrismaService); diff --git a/apps/swap-service/src/prisma/prisma.service.ts b/apps/swap-service/src/prisma/prisma.service.ts index bb6565f..7ffd32d 100644 --- a/apps/swap-service/src/prisma/prisma.service.ts +++ b/apps/swap-service/src/prisma/prisma.service.ts @@ -2,7 +2,10 @@ import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; @Injectable() -export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { +export class PrismaService + extends PrismaClient + implements OnModuleInit, OnModuleDestroy +{ async onModuleInit() { await this.$connect(); } diff --git a/apps/swap-service/src/swaps/swaps.controller.js b/apps/swap-service/src/swaps/swaps.controller.js new file mode 100644 index 0000000..5f67681 --- /dev/null +++ b/apps/swap-service/src/swaps/swaps.controller.js @@ -0,0 +1,162 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SwapsController = exports.Prisma = exports.Swap = void 0; +const common_1 = require("@nestjs/common"); +const swaps_service_1 = require("./swaps.service"); +const swap_polling_service_1 = require("../polling/swap-polling.service"); +const swap_verification_service_1 = require("../verification/swap-verification.service"); +var client_1 = require("@prisma/client"); +Object.defineProperty(exports, "Swap", { enumerable: true, get: function () { return client_1.Swap; } }); +Object.defineProperty(exports, "Prisma", { enumerable: true, get: function () { return client_1.Prisma; } }); +let SwapsController = class SwapsController { + constructor(swapsService, swapPollingService, swapVerificationService) { + this.swapsService = swapsService; + this.swapPollingService = swapPollingService; + this.swapVerificationService = swapVerificationService; + } + async createSwap(data) { + return this.swapsService.createSwap(data); + } + async updateSwapStatus(swapId, data) { + return this.swapsService.updateSwapStatus({ + swapId, + ...data, + }); + } + async getSwapsByUser(userId, limit) { + return this.swapsService.getSwapsByUser(userId, limit ? parseInt(limit) : 50); + } + async getSwapsByAccountId(accountId) { + return this.swapsService.getSwapsByAccountId(accountId); + } + async getPendingSwaps() { + return this.swapsService.getPendingSwaps(); + } + async getReferralFees(referralCode, startDate, endDate) { + const start = startDate ? new Date(startDate) : undefined; + const end = endDate ? new Date(endDate) : undefined; + return this.swapsService.calculateReferralFees(referralCode, start, end); + } + async getAffiliateFees(affiliateAddress, startDate, endDate) { + const start = startDate ? new Date(startDate) : undefined; + const end = endDate ? new Date(endDate) : undefined; + return this.swapsService.calculateAffiliateFees(affiliateAddress, start, end); + } + async getSwap(swapId) { + const swap = await this.swapsService['prisma'].swap.findUnique({ + where: { swapId }, + }); + if (!swap) { + return null; + } + return { + ...swap, + sellAsset: swap.sellAsset, + buyAsset: swap.buyAsset, + }; + } + async verifySwapAffiliate(swapId, data) { + // Fetch the swap to get metadata and other details + const swap = await this.swapsService['prisma'].swap.findUnique({ + where: { swapId }, + }); + if (!swap) { + return { + isVerified: false, + hasAffiliate: false, + protocol: data.protocol, + swapId, + error: 'Swap not found', + }; + } + return this.swapVerificationService.verifySwapAffiliate(swapId, data.protocol || swap.swapperName, swap.sellAsset.chainId, data.txHash || swap.sellTxHash || undefined, swap.metadata); + } +}; +exports.SwapsController = SwapsController; +__decorate([ + (0, common_1.Post)(), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], SwapsController.prototype, "createSwap", null); +__decorate([ + (0, common_1.Put)(':swapId/status'), + __param(0, (0, common_1.Param)('swapId')), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String, Object]), + __metadata("design:returntype", Promise) +], SwapsController.prototype, "updateSwapStatus", null); +__decorate([ + (0, common_1.Get)('user/:userId'), + __param(0, (0, common_1.Param)('userId')), + __param(1, (0, common_1.Query)('limit')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String, String]), + __metadata("design:returntype", Promise) +], SwapsController.prototype, "getSwapsByUser", null); +__decorate([ + (0, common_1.Get)('account/:accountId'), + __param(0, (0, common_1.Param)('accountId')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", Promise) +], SwapsController.prototype, "getSwapsByAccountId", null); +__decorate([ + (0, common_1.Get)('pending'), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", Promise) +], SwapsController.prototype, "getPendingSwaps", null); +__decorate([ + (0, common_1.Get)('referral-fees/:referralCode'), + __param(0, (0, common_1.Param)('referralCode')), + __param(1, (0, common_1.Query)('startDate')), + __param(2, (0, common_1.Query)('endDate')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String, String, String]), + __metadata("design:returntype", Promise) +], SwapsController.prototype, "getReferralFees", null); +__decorate([ + (0, common_1.Get)('affiliate-fees/:affiliateAddress'), + __param(0, (0, common_1.Param)('affiliateAddress')), + __param(1, (0, common_1.Query)('startDate')), + __param(2, (0, common_1.Query)('endDate')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String, String, String]), + __metadata("design:returntype", Promise) +], SwapsController.prototype, "getAffiliateFees", null); +__decorate([ + (0, common_1.Get)(':swapId'), + __param(0, (0, common_1.Param)('swapId')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", Promise) +], SwapsController.prototype, "getSwap", null); +__decorate([ + (0, common_1.Post)(':swapId/verify-affiliate'), + __param(0, (0, common_1.Param)('swapId')), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String, Object]), + __metadata("design:returntype", Promise) +], SwapsController.prototype, "verifySwapAffiliate", null); +exports.SwapsController = SwapsController = __decorate([ + (0, common_1.Controller)('swaps'), + __metadata("design:paramtypes", [swaps_service_1.SwapsService, + swap_polling_service_1.SwapPollingService, + swap_verification_service_1.SwapVerificationService]) +], SwapsController); diff --git a/apps/swap-service/src/swaps/swaps.service.js b/apps/swap-service/src/swaps/swaps.service.js new file mode 100644 index 0000000..700a3ca --- /dev/null +++ b/apps/swap-service/src/swaps/swaps.service.js @@ -0,0 +1,708 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var SwapsService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SwapsService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../prisma/prisma.service"); +const evm_service_1 = require("../lib/chain-adapters/evm.service"); +const utxo_service_1 = require("../lib/chain-adapters/utxo.service"); +const cosmos_sdk_service_1 = require("../lib/chain-adapters/cosmos-sdk.service"); +const solana_service_1 = require("../lib/chain-adapters/solana.service"); +const swap_verification_service_1 = require("../verification/swap-verification.service"); +const swapper_1 = require("@shapeshiftoss/swapper"); +const shared_utils_1 = require("@shapeshift/shared-utils"); +const affiliateFeeAsset_1 = require("../utils/affiliateFeeAsset"); +const shared_utils_2 = require("@shapeshift/shared-utils"); +const chain_adapters_1 = require("@shapeshiftoss/chain-adapters"); +const unchained_client_1 = require("@shapeshiftoss/unchained-client"); +let SwapsService = SwapsService_1 = class SwapsService { + constructor(prisma, evmChainAdapterService, utxoChainAdapterService, cosmosSdkChainAdapterService, solanaChainAdapterService, swapVerificationService) { + this.prisma = prisma; + this.evmChainAdapterService = evmChainAdapterService; + this.utxoChainAdapterService = utxoChainAdapterService; + this.cosmosSdkChainAdapterService = cosmosSdkChainAdapterService; + this.solanaChainAdapterService = solanaChainAdapterService; + this.swapVerificationService = swapVerificationService; + this.logger = new common_1.Logger(SwapsService_1.name); + this.notificationsClient = new shared_utils_2.NotificationsServiceClient(); + this.userServiceClient = new shared_utils_2.UserServiceClient(); + } + async createSwap(data) { + try { + let referralCode = null; + if (data.userId) { + try { + referralCode = await this.userServiceClient.getUserReferralCode(data.userId); + if (referralCode) { + this.logger.log(`Found referral code ${referralCode} for user ${data.userId}`); + } + } + catch (error) { + this.logger.warn(`Failed to fetch referral code for user ${data.userId}:`, error); + } + } + let sellAmountUsd = null; + try { + const { getAssetPriceUsd, calculateUsdValue } = await Promise.resolve().then(() => __importStar(require('../utils/pricing'))); + const price = await getAssetPriceUsd(data.sellAsset); + if (price) { + sellAmountUsd = calculateUsdValue(data.sellAmountCryptoPrecision, price); + } + } + catch (error) { + this.logger.warn(`Failed to calculate sellAmountUsd for swap ${data.swapId}:`, error); + } + const affiliateFeeAssetId = (0, affiliateFeeAsset_1.resolveAffiliateFeeAssetId)(data.swapperName, data.sellAsset, data.buyAsset); + const swap = await this.prisma.swap.create({ + data: { + swapId: data.swapId, + sellAsset: data.sellAsset, + buyAsset: data.buyAsset, + sellTxHash: data.sellTxHash || null, + sellAmountCryptoBaseUnit: data.sellAmountCryptoBaseUnit, + expectedBuyAmountCryptoBaseUnit: data.expectedBuyAmountCryptoBaseUnit, + sellAmountCryptoPrecision: data.sellAmountCryptoPrecision, + expectedBuyAmountCryptoPrecision: data.expectedBuyAmountCryptoPrecision, + source: data.source, + swapperName: data.swapperName, + sellAccountId: data.sellAccountId + ? (0, shared_utils_1.hashAccountId)(data.sellAccountId) + : 'api', + buyAccountId: data.buyAccountId + ? (0, shared_utils_1.hashAccountId)(data.buyAccountId) + : null, + receiveAddress: data.receiveAddress, + isStreaming: data.isStreaming || false, + metadata: data.metadata || {}, + userId: data.userId || 'api', + referralCode, + sellAmountUsd, + affiliateAddress: data.affiliateAddress || null, + affiliateBps: data.affiliateBps || null, + origin: data.origin || null, + affiliateFeeAssetId, + }, + }); + this.logger.log(`Swap created: ${swap.id}` + + `${referralCode ? ` with referral code ${referralCode}` : ''}` + + `${data.affiliateAddress ? ` with affiliate ${data.affiliateAddress}` : ''}` + + `${sellAmountUsd ? ` ($${sellAmountUsd})` : ''}`); + return swap; + } + catch (error) { + this.logger.error('Failed to create swap', error); + throw error; + } + } + async updateSwapStatus(data) { + try { + const swap = await this.prisma.swap.update({ + where: { swapId: data.swapId }, + data: { + status: data.status, + sellTxHash: data.sellTxHash, + buyTxHash: data.buyTxHash, + txLink: data.txLink, + statusMessage: data.statusMessage, + actualBuyAmountCryptoPrecision: data.actualBuyAmountCryptoPrecision, + }, + }); + await this.sendStatusUpdateNotification(swap); + this.logger.log(`Swap status updated: ${swap.swapId} -> ${data.status}`); + return { + ...swap, + sellAsset: swap.sellAsset, + buyAsset: swap.buyAsset, + }; + } + catch (error) { + this.logger.error('Failed to update swap status', error); + throw error; + } + } + formatAmount(amount) { + // Convert to number with up to 8 decimals, then remove trailing zeros + const num = (0, chain_adapters_1.bnOrZero)(amount).toFixed(8); + // Remove trailing zeros and trailing decimal point + return num.replace(/\.?0+$/, ''); + } + async sendStatusUpdateNotification(swap) { + let title; + let body; + let type; + const sellAsset = swap.sellAsset; + const buyAsset = swap.buyAsset; + switch (swap.status) { + case 'SUCCESS': { + title = 'Swap Completed!'; + const buyAmount = this.formatAmount(swap.actualBuyAmountCryptoPrecision || + swap.expectedBuyAmountCryptoPrecision); + body = `Your swap of ${this.formatAmount(swap.sellAmountCryptoPrecision)} ${sellAsset.symbol} to ${buyAmount} ${buyAsset.symbol} is complete.`; + type = 'SWAP_COMPLETED'; + break; + } + case 'FAILED': + title = 'Swap Failed'; + body = `Your ${sellAsset.symbol} to ${buyAsset.symbol} swap has failed`; + type = 'SWAP_FAILED'; + break; + default: + return; + } + if (swap.status === 'FAILED' || swap.status === 'SUCCESS') { + await this.notificationsClient.createNotification({ + userId: swap.userId, + title, + body, + type, + swapId: swap.id, + }); + } + } + async getSwapsByUser(userId, limit = 50) { + const swaps = await this.prisma.swap.findMany({ + where: { userId }, + orderBy: { createdAt: 'desc' }, + take: limit, + }); + return swaps.map((swap) => ({ + ...swap, + sellAsset: swap.sellAsset, + buyAsset: swap.buyAsset, + })); + } + async getSwapsByAccountId(accountId) { + const hashedAccountId = (0, shared_utils_1.hashAccountId)(accountId); + const swaps = await this.prisma.swap.findMany({ + where: { + OR: [ + { sellAccountId: hashedAccountId }, + { buyAccountId: hashedAccountId }, + ], + }, + }); + return swaps.map((swap) => ({ + ...swap, + sellAsset: swap.sellAsset, + buyAsset: swap.buyAsset, + })); + } + async getPendingSwaps() { + const swaps = await this.prisma.swap.findMany({ + where: { + status: { + in: ['IDLE', 'PENDING'], + }, + sellTxHash: { not: null }, + }, + }); + return swaps.map((swap) => ({ + ...swap, + sellAsset: swap.sellAsset, + buyAsset: swap.buyAsset, + })); + } + async calculateReferralFees(referralCode, startDate, endDate) { + this.logger.log(`Calculating referral fees for code: ${referralCode}, period: ${startDate?.toISOString()} - ${endDate?.toISOString()}`); + // Fetch swaps for the current period + const periodWhereClause = { + referralCode, + isAffiliateVerified: true, + status: 'SUCCESS', + }; + if (startDate && endDate) { + periodWhereClause.createdAt = { + gte: startDate, + lte: endDate, + }; + } + const periodSwaps = await this.prisma.swap.findMany({ + where: periodWhereClause, + select: { + id: true, + swapId: true, + sellAsset: true, + sellAmountCryptoPrecision: true, + affiliateVerificationDetails: true, + createdAt: true, + }, + }); + // Fetch ALL swaps since the start (for total fees collected by referrer) + const allTimeSwaps = await this.prisma.swap.findMany({ + where: { + referralCode, + isAffiliateVerified: true, + status: 'SUCCESS', + }, + select: { + id: true, + swapId: true, + sellAsset: true, + sellAmountCryptoPrecision: true, + affiliateVerificationDetails: true, + createdAt: true, + }, + }); + this.logger.log(`Found ${periodSwaps.length} swaps for period, ${allTimeSwaps.length} swaps all-time for referral code ${referralCode}`); + let periodFeesUsd = 0; + let totalSwapVolumeUsd = 0; + const swapCount = periodSwaps.length; + // Import pricing utilities dynamically + const { getAssetPriceUsd, calculateUsdValue } = await Promise.resolve().then(() => __importStar(require('../utils/pricing'))); + // Fetch prices for all unique assets from both period and all-time swaps + const uniqueAssets = new Map(); + for (const swap of [...periodSwaps, ...allTimeSwaps]) { + const sellAsset = swap.sellAsset; + if (!uniqueAssets.has(sellAsset.assetId)) { + uniqueAssets.set(sellAsset.assetId, sellAsset); + } + } + // Fetch all prices in parallel + const pricePromises = Array.from(uniqueAssets.values()).map(async (asset) => { + const price = await getAssetPriceUsd(asset); + return { assetId: asset.assetId, price }; + }); + const prices = await Promise.all(pricePromises); + const priceMap = new Map(); + prices.forEach(({ assetId, price }) => { + priceMap.set(assetId, price); + }); + // Calculate period fees and volume + for (const swap of periodSwaps) { + const sellAsset = swap.sellAsset; + const price = priceMap.get(sellAsset.assetId); + if (!price) { + this.logger.warn(`No price found for asset ${sellAsset.assetId}, skipping swap ${swap.swapId}`); + continue; + } + const sellAmountUsd = parseFloat(calculateUsdValue(swap.sellAmountCryptoPrecision, price)); + totalSwapVolumeUsd += sellAmountUsd; + // Extract affiliateBps from verification details + const verificationDetails = swap.affiliateVerificationDetails; + const affiliateBps = verificationDetails?.affiliateBps; + if (affiliateBps && sellAmountUsd > 0) { + // Fee = (sellAmountUsd × affiliateBps) / 10,000 + const feeUsd = (sellAmountUsd * affiliateBps) / 10000; + periodFeesUsd += feeUsd; + } + } + // Calculate all-time fees (for totalFeesCollectedUsd which represents total referrer earnings) + let allTimeFeesUsd = 0; + for (const swap of allTimeSwaps) { + const sellAsset = swap.sellAsset; + const price = priceMap.get(sellAsset.assetId); + if (!price) + continue; + const sellAmountUsd = parseFloat(calculateUsdValue(swap.sellAmountCryptoPrecision, price)); + const verificationDetails = swap.affiliateVerificationDetails; + const affiliateBps = verificationDetails?.affiliateBps; + if (affiliateBps && sellAmountUsd > 0) { + const feeUsd = (sellAmountUsd * affiliateBps) / 10000; + allTimeFeesUsd += feeUsd; + } + } + // Calculate referrer's 10% commission + const periodReferrerCommissionUsd = periodFeesUsd * 0.1; + const allTimeReferrerCommissionUsd = allTimeFeesUsd * 0.1; + this.logger.log(`Referral fee calculation for ${referralCode}: ` + + `Period: ${swapCount} swaps, $${totalSwapVolumeUsd.toFixed(2)} volume, $${periodReferrerCommissionUsd.toFixed(2)} commission | ` + + `All-time: ${allTimeSwaps.length} swaps, $${allTimeReferrerCommissionUsd.toFixed(2)} total commission`); + return { + referralCode, + swapCount, + totalSwapVolumeUsd: totalSwapVolumeUsd.toFixed(2), + totalFeesCollectedUsd: allTimeReferrerCommissionUsd.toFixed(2), // Total referrer earnings all-time + referrerCommissionUsd: periodReferrerCommissionUsd.toFixed(2), // Period referrer earnings + periodStart: startDate?.toISOString(), + periodEnd: endDate?.toISOString(), + }; + } + getDistributionCutoff() { + const now = new Date(); + const cutoff = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 5)); + if (now >= cutoff) + return cutoff; + return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - 1, 5)); + } + async resolveSwapUsdValue(swap, priceMap, freezeCutoff, calculateUsdValue) { + const sellAsset = swap.sellAsset; + const isFrozen = swap.createdAt < freezeCutoff; + if (isFrozen && swap.sellAmountUsd) { + return parseFloat(swap.sellAmountUsd); + } + const price = priceMap.get(sellAsset.assetId); + if (!price) { + this.logger.warn(`No price found for asset ${sellAsset.assetId}, skipping swap ${swap.swapId}`); + return null; + } + const liveUsdValue = parseFloat(calculateUsdValue(swap.sellAmountCryptoPrecision, price)); + if (isFrozen && !swap.sellAmountUsd) { + await this.prisma.swap.update({ + where: { id: swap.id }, + data: { sellAmountUsd: liveUsdValue.toFixed(2) }, + }); + this.logger.log(`Froze sellAmountUsd=${liveUsdValue.toFixed(2)} for swap ${swap.swapId} (pre-distribution)`); + } + return liveUsdValue; + } + async calculateAffiliateFees(affiliateAddress, startDate, endDate) { + this.logger.log(`Calculating affiliate fees for address: ${affiliateAddress}, period: ${startDate?.toISOString()} - ${endDate?.toISOString()}`); + const freezeCutoff = this.getDistributionCutoff(); + this.logger.log(`Distribution freeze cutoff: ${freezeCutoff.toISOString()}`); + const periodWhereClause = { + affiliateAddress, + isAffiliateVerified: true, + status: 'SUCCESS', + }; + if (startDate && endDate) { + periodWhereClause.createdAt = { + gte: startDate, + lte: endDate, + }; + } + const swapSelect = { + id: true, + swapId: true, + swapperName: true, + sellAsset: true, + buyAsset: true, + sellAmountCryptoBaseUnit: true, + sellAmountCryptoPrecision: true, + expectedBuyAmountCryptoBaseUnit: true, + sellAmountUsd: true, + affiliateBps: true, + origin: true, + affiliateFeeAssetId: true, + affiliateFeeAmountCryptoBaseUnit: true, + affiliateVerificationDetails: true, + createdAt: true, + }; + const periodSwaps = await this.prisma.swap.findMany({ + where: periodWhereClause, + select: swapSelect, + }); + const allTimeSwaps = await this.prisma.swap.findMany({ + where: { + affiliateAddress, + isAffiliateVerified: true, + status: 'SUCCESS', + }, + select: swapSelect, + }); + this.logger.log(`Found ${periodSwaps.length} swaps for period, ${allTimeSwaps.length} swaps all-time for affiliate ${affiliateAddress}`); + const { getAssetPriceUsd, calculateUsdValue } = await Promise.resolve().then(() => __importStar(require('../utils/pricing'))); + const uniqueAssets = new Map(); + for (const swap of [...periodSwaps, ...allTimeSwaps]) { + const sellAsset = swap.sellAsset; + const buyAsset = swap.buyAsset; + if (!uniqueAssets.has(sellAsset.assetId)) { + uniqueAssets.set(sellAsset.assetId, sellAsset); + } + if (!uniqueAssets.has(buyAsset.assetId)) { + uniqueAssets.set(buyAsset.assetId, buyAsset); + } + } + const pricePromises = Array.from(uniqueAssets.values()).map(async (asset) => { + const price = await getAssetPriceUsd(asset); + return { assetId: asset.assetId, price }; + }); + const prices = await Promise.all(pricePromises); + const priceMap = new Map(); + prices.forEach(({ assetId, price }) => { + priceMap.set(assetId, price); + }); + let periodCommissionUsd = 0; + let totalSwapVolumeUsd = 0; + const swapCount = periodSwaps.length; + for (const swap of periodSwaps) { + const sellAmountUsd = await this.resolveSwapUsdValue(swap, priceMap, freezeCutoff, calculateUsdValue); + if (sellAmountUsd === null) + continue; + totalSwapVolumeUsd += sellAmountUsd; + const verificationDetails = swap.affiliateVerificationDetails; + const verifiedBps = verificationDetails?.affiliateBps; + if (verifiedBps && sellAmountUsd > 0) { + const commissionRate = this.getAffiliateCommissionRate(swap.origin, verifiedBps); + const verifiedSell = verificationDetails?.verifiedSellAmountCryptoBaseUnit; + const effectiveSellAmount = verifiedSell + ? (0, chain_adapters_1.bnOrZero)(verifiedSell).lt((0, chain_adapters_1.bnOrZero)(swap.sellAmountCryptoBaseUnit)) + ? verifiedSell + : swap.sellAmountCryptoBaseUnit + : swap.sellAmountCryptoBaseUnit; + let totalFeeUsd; + const feeAssetId = swap.affiliateFeeAssetId; + const feeAssetPrice = feeAssetId ? priceMap.get(feeAssetId) : null; + if (feeAssetId && feeAssetPrice) { + const sellAssetObj = swap.sellAsset; + const buyAssetObj = swap.buyAsset; + const feeAmountBaseUnit = this.estimateAffiliateFeeAmount(verifiedBps, swap.swapperName, effectiveSellAmount, swap.expectedBuyAmountCryptoBaseUnit); + const feeAssetPrecision = feeAssetId === sellAssetObj.assetId + ? sellAssetObj.precision + : buyAssetObj.precision; + const feeAmountHuman = parseFloat(feeAmountBaseUnit) / Math.pow(10, feeAssetPrecision); + totalFeeUsd = feeAmountHuman * feeAssetPrice; + } + else { + totalFeeUsd = (sellAmountUsd * verifiedBps) / 10000; + } + periodCommissionUsd += totalFeeUsd * commissionRate; + } + } + let allTimeCommissionUsd = 0; + for (const swap of allTimeSwaps) { + const sellAmountUsd = await this.resolveSwapUsdValue(swap, priceMap, freezeCutoff, calculateUsdValue); + if (sellAmountUsd === null) + continue; + const verificationDetails = swap.affiliateVerificationDetails; + const verifiedBps = verificationDetails?.affiliateBps; + if (verifiedBps && sellAmountUsd > 0) { + const commissionRate = this.getAffiliateCommissionRate(swap.origin, verifiedBps); + const verifiedSell = verificationDetails?.verifiedSellAmountCryptoBaseUnit; + const effectiveSellAmount = verifiedSell + ? (0, chain_adapters_1.bnOrZero)(verifiedSell).lt((0, chain_adapters_1.bnOrZero)(swap.sellAmountCryptoBaseUnit)) + ? verifiedSell + : swap.sellAmountCryptoBaseUnit + : swap.sellAmountCryptoBaseUnit; + let totalFeeUsd; + const feeAssetId = swap.affiliateFeeAssetId; + const feeAssetPrice = feeAssetId ? priceMap.get(feeAssetId) : null; + if (feeAssetId && feeAssetPrice) { + const sellAssetObj = swap.sellAsset; + const buyAssetObj = swap.buyAsset; + const feeAmountBaseUnit = this.estimateAffiliateFeeAmount(verifiedBps, swap.swapperName, effectiveSellAmount, swap.expectedBuyAmountCryptoBaseUnit); + const feeAssetPrecision = feeAssetId === sellAssetObj.assetId + ? sellAssetObj.precision + : buyAssetObj.precision; + const feeAmountHuman = parseFloat(feeAmountBaseUnit) / Math.pow(10, feeAssetPrecision); + totalFeeUsd = feeAmountHuman * feeAssetPrice; + } + else { + totalFeeUsd = (sellAmountUsd * verifiedBps) / 10000; + } + allTimeCommissionUsd += totalFeeUsd * commissionRate; + } + } + this.logger.log(`Affiliate fee calculation for ${affiliateAddress}: ` + + `Period: ${swapCount} swaps, $${totalSwapVolumeUsd.toFixed(2)} volume, $${periodCommissionUsd.toFixed(2)} commission | ` + + `All-time: ${allTimeSwaps.length} swaps, $${allTimeCommissionUsd.toFixed(2)} total commission`); + return { + affiliateAddress, + swapCount, + totalSwapVolumeUsd: totalSwapVolumeUsd.toFixed(2), + totalFeesCollectedUsd: allTimeCommissionUsd.toFixed(2), + referrerCommissionUsd: periodCommissionUsd.toFixed(2), + periodStart: startDate?.toISOString(), + periodEnd: endDate?.toISOString(), + }; + } + getAffiliateCommissionRate(origin, verifiedBps) { + if (origin === 'web') { + return SwapsService_1.WEB_REVENUE_SHARE; + } + if (!origin || verifiedBps <= SwapsService_1.API_BASE_BPS) + return 0; + return (verifiedBps - SwapsService_1.API_BASE_BPS) / verifiedBps; + } + estimateAffiliateFeeAmount(affiliateBps, swapperName, sellAmountCryptoBaseUnit, expectedBuyAmountCryptoBaseUnit) { + const strategy = (0, affiliateFeeAsset_1.getSwapperFeeStrategy)(swapperName); + const bpsMultiplier = affiliateBps / 10000; + switch (strategy) { + case 'sell_asset': + return (0, chain_adapters_1.bnOrZero)(sellAmountCryptoBaseUnit) + .times(bpsMultiplier) + .toFixed(0); + case 'buy_asset': + return (0, chain_adapters_1.bnOrZero)(expectedBuyAmountCryptoBaseUnit) + .times(bpsMultiplier) + .toFixed(0); + case 'fixed_base': + default: + return (0, chain_adapters_1.bnOrZero)(sellAmountCryptoBaseUnit) + .times(bpsMultiplier) + .toFixed(0); + } + } + async pollSwapStatus(swapId) { + try { + this.logger.log(`Polling status for swap: ${swapId}`); + const swap = await this.prisma.swap.findUnique({ + where: { swapId }, + }); + if (!swap) { + throw new Error(`Swap not found: ${swapId}`); + } + const sellAsset = swap.sellAsset; + const swapper = swapper_1.swappers[swap.swapperName]; + if (!swapper) { + throw new Error(`Swapper not found: ${swap.swapperName}`); + } + if (!swap.sellTxHash) { + throw new Error('Sell tx hash is required'); + } + const status = await swapper.checkTradeStatus({ + txHash: swap.sellTxHash ?? '', + chainId: sellAsset.chainId, + address: swap.sellAccountId, + swap: { + ...swap, + id: swap.swapId, + createdAt: swap.createdAt.getTime(), + updatedAt: swap.updatedAt.getTime(), + }, + stepIndex: 0, + config: { + VITE_UNCHAINED_THORCHAIN_HTTP_URL: process.env.VITE_UNCHAINED_THORCHAIN_HTTP_URL || '', + VITE_UNCHAINED_MAYACHAIN_HTTP_URL: process.env.VITE_UNCHAINED_MAYACHAIN_HTTP_URL || '', + VITE_UNCHAINED_COSMOS_HTTP_URL: process.env.VITE_UNCHAINED_COSMOS_HTTP_URL || '', + VITE_THORCHAIN_NODE_URL: process.env.VITE_THORCHAIN_NODE_URL || '', + VITE_MAYACHAIN_NODE_URL: process.env.VITE_MAYACHAIN_NODE_URL || '', + VITE_COWSWAP_BASE_URL: process.env.VITE_COWSWAP_BASE_URL || '', + VITE_CHAINFLIP_API_KEY: process.env.VITE_CHAINFLIP_API_KEY || '', + VITE_CHAINFLIP_API_URL: process.env.VITE_CHAINFLIP_API_URL || '', + VITE_JUPITER_API_URL: process.env.VITE_JUPITER_API_URL || '', + VITE_RELAY_API_URL: process.env.VITE_RELAY_API_URL || '', + VITE_PORTALS_BASE_URL: process.env.VITE_PORTALS_BASE_URL || '', + VITE_ZRX_BASE_URL: process.env.VITE_ZRX_BASE_URL || '', + VITE_THORCHAIN_MIDGARD_URL: process.env.VITE_THORCHAIN_MIDGARD_URL || '', + VITE_MAYACHAIN_MIDGARD_URL: process.env.VITE_MAYACHAIN_MIDGARD_URL || '', + VITE_UNCHAINED_BITCOIN_HTTP_URL: process.env.VITE_UNCHAINED_BITCOIN_HTTP_URL || '', + VITE_UNCHAINED_DOGECOIN_HTTP_URL: process.env.VITE_UNCHAINED_DOGECOIN_HTTP_URL || '', + VITE_UNCHAINED_LITECOIN_HTTP_URL: process.env.VITE_UNCHAINED_LITECOIN_HTTP_URL || '', + VITE_UNCHAINED_BITCOINCASH_HTTP_URL: process.env.VITE_UNCHAINED_BITCOINCASH_HTTP_URL || '', + VITE_UNCHAINED_ETHEREUM_HTTP_URL: process.env.VITE_UNCHAINED_ETHEREUM_HTTP_URL || '', + VITE_UNCHAINED_AVALANCHE_HTTP_URL: process.env.VITE_UNCHAINED_AVALANCHE_HTTP_URL || '', + VITE_UNCHAINED_BNBSMARTCHAIN_HTTP_URL: process.env.VITE_UNCHAINED_BNBSMARTCHAIN_HTTP_URL || '', + VITE_UNCHAINED_BASE_HTTP_URL: process.env.VITE_UNCHAINED_BASE_HTTP_URL || '', + VITE_NEAR_INTENTS_API_KEY: process.env.VITE_NEAR_INTENTS_API_KEY || '', + VITE_FEATURE_THORCHAINSWAP_LONGTAIL: true, + VITE_FEATURE_THORCHAINSWAP_L1_TO_LONGTAIL: true, + VITE_FEATURE_CHAINFLIP_SWAP_DCA: true, + }, + assertGetSolanaChainAdapter: (chainId) => { + return this.solanaChainAdapterService.assertGetSolanaChainAdapter(chainId); + }, + assertGetUtxoChainAdapter: (chainId) => { + return this.utxoChainAdapterService.assertGetUtxoChainAdapter(chainId); + }, + assertGetCosmosSdkChainAdapter: (chainId) => { + return this.cosmosSdkChainAdapterService.assertGetCosmosSdkChainAdapter(chainId); + }, + assertGetEvmChainAdapter: (chainId) => { + return this.evmChainAdapterService.assertGetEvmChainAdapter(chainId); + }, + fetchIsSmartContractAddressQuery: () => Promise.resolve(false), + }); + // Verify affiliate usage + let isAffiliateVerified; + let affiliateVerificationDetails; + try { + // Enrich metadata with swap fields needed for verification + const enrichedMetadata = { + ...swap.metadata, + receiveAddress: swap.receiveAddress, + expectedBuyAmountCryptoPrecision: swap.expectedBuyAmountCryptoPrecision, + createdAt: swap.createdAt.getTime(), + sellAssetPrecision: sellAsset.precision, + }; + const verificationResult = await this.swapVerificationService.verifySwapAffiliate(swapId, swap.swapperName, sellAsset.chainId, swap.sellTxHash || undefined, enrichedMetadata); + isAffiliateVerified = + verificationResult.isVerified && verificationResult.hasAffiliate; + if (verificationResult.isVerified) { + affiliateVerificationDetails = { + hasAffiliate: verificationResult.hasAffiliate, + affiliateBps: verificationResult.affiliateBps, + affiliateAddress: verificationResult.affiliateAddress, + verifiedSellAmountCryptoBaseUnit: verificationResult.verifiedSellAmountCryptoBaseUnit, + }; + } + this.logger.log(`Affiliate verification for swap ${swapId}: verified=${verificationResult.isVerified}, hasAffiliate=${verificationResult.hasAffiliate}`); + // Update the database with verification result + await this.prisma.swap.update({ + where: { swapId }, + data: { + isAffiliateVerified, + affiliateVerificationDetails: affiliateVerificationDetails || {}, + affiliateVerifiedAt: new Date(), + }, + }); + } + catch (verificationError) { + this.logger.warn(`Failed to verify affiliate for swap ${swapId}:`, verificationError); + // Don't fail the entire status check if verification fails + } + return { + status: status.status === unchained_client_1.TxStatus.Confirmed + ? 'SUCCESS' + : status.status === unchained_client_1.TxStatus.Failed + ? 'FAILED' + : 'PENDING', + sellTxHash: swap.sellTxHash, + buyTxHash: status.buyTxHash, + statusMessage: status.message, + isAffiliateVerified, + affiliateVerificationDetails, + }; + } + catch (error) { + this.logger.error(`Failed to poll swap status for ${swapId}:`, error); + return { + status: 'PENDING', + statusMessage: `Error polling status: ${error instanceof Error ? error.message : 'Unknown error'}`, + }; + } + } +}; +exports.SwapsService = SwapsService; +SwapsService.API_BASE_BPS = 10; +SwapsService.WEB_REVENUE_SHARE = 0.1; +exports.SwapsService = SwapsService = SwapsService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService, + evm_service_1.EvmChainAdapterService, + utxo_service_1.UtxoChainAdapterService, + cosmos_sdk_service_1.CosmosSdkChainAdapterService, + solana_service_1.SolanaChainAdapterService, + swap_verification_service_1.SwapVerificationService]) +], SwapsService); diff --git a/apps/swap-service/src/swaps/swaps.service.ts b/apps/swap-service/src/swaps/swaps.service.ts index e4ae8c5..ced07d8 100644 --- a/apps/swap-service/src/swaps/swaps.service.ts +++ b/apps/swap-service/src/swaps/swaps.service.ts @@ -6,15 +6,14 @@ import { UtxoChainAdapterService } from '../lib/chain-adapters/utxo.service'; import { CosmosSdkChainAdapterService } from '../lib/chain-adapters/cosmos-sdk.service'; import { SolanaChainAdapterService } from '../lib/chain-adapters/solana.service'; import { SwapVerificationService } from '../verification/swap-verification.service'; -import { - SwapperName, - swappers, - SwapSource, - SwapStatus, -} from '@shapeshiftoss/swapper'; +import { SwapperName, swappers } from '@shapeshiftoss/swapper'; import { ChainId } from '@shapeshiftoss/caip'; import { Asset } from '@shapeshiftoss/types'; import { hashAccountId } from '@shapeshift/shared-utils'; +import { + resolveAffiliateFeeAssetId, + getSwapperFeeStrategy, +} from '../utils/affiliateFeeAsset'; import { NotificationsServiceClient, UserServiceClient, @@ -25,6 +24,14 @@ import { UpdateSwapStatusDto, } from '@shapeshift/shared-types'; import { bnOrZero } from '@shapeshiftoss/chain-adapters'; +import { TxStatus } from '@shapeshiftoss/unchained-client'; + +type AffiliateVerificationDetails = { + affiliateBps?: number; + affiliateAddress?: string; + verifiedSellAmountCryptoBaseUnit?: string; + hasAffiliate?: boolean; +}; @Injectable() export class SwapsService { @@ -84,12 +91,18 @@ export class SwapsService { ); } + const affiliateFeeAssetId = resolveAffiliateFeeAssetId( + data.swapperName, + data.sellAsset, + data.buyAsset, + ); + const swap = await this.prisma.swap.create({ data: { swapId: data.swapId, sellAsset: data.sellAsset, buyAsset: data.buyAsset, - sellTxHash: data.sellTxHash, + sellTxHash: data.sellTxHash || null, sellAmountCryptoBaseUnit: data.sellAmountCryptoBaseUnit, expectedBuyAmountCryptoBaseUnit: data.expectedBuyAmountCryptoBaseUnit, sellAmountCryptoPrecision: data.sellAmountCryptoPrecision, @@ -97,17 +110,22 @@ export class SwapsService { data.expectedBuyAmountCryptoPrecision, source: data.source, swapperName: data.swapperName, - sellAccountId: hashAccountId(data.sellAccountId), + sellAccountId: data.sellAccountId + ? hashAccountId(data.sellAccountId) + : 'api', buyAccountId: data.buyAccountId ? hashAccountId(data.buyAccountId) : null, receiveAddress: data.receiveAddress, isStreaming: data.isStreaming || false, metadata: data.metadata || {}, - userId: data.userId, + userId: data.userId || 'api', referralCode, sellAmountUsd, affiliateAddress: data.affiliateAddress || null, + affiliateBps: data.affiliateBps || null, + origin: data.origin || null, + affiliateFeeAssetId, }, }); @@ -180,7 +198,7 @@ export class SwapsService { const buyAsset = swap.buyAsset as Asset; switch (swap.status) { - case 'SUCCESS': + case 'SUCCESS': { title = 'Swap Completed!'; const buyAmount = this.formatAmount( swap.actualBuyAmountCryptoPrecision || @@ -189,6 +207,7 @@ export class SwapsService { body = `Your swap of ${this.formatAmount(swap.sellAmountCryptoPrecision)} ${sellAsset.symbol} to ${buyAmount} ${buyAsset.symbol} is complete.`; type = 'SWAP_COMPLETED'; break; + } case 'FAILED': title = 'Swap Failed'; body = `Your ${sellAsset.symbol} to ${buyAsset.symbol} swap has failed`; @@ -247,6 +266,7 @@ export class SwapsService { status: { in: ['IDLE', 'PENDING'], }, + sellTxHash: { not: null }, }, }); @@ -267,7 +287,7 @@ export class SwapsService { ); // Fetch swaps for the current period - const periodWhereClause: any = { + const periodWhereClause: Prisma.SwapWhereInput = { referralCode, isAffiliateVerified: true, status: 'SUCCESS', @@ -363,7 +383,8 @@ export class SwapsService { totalSwapVolumeUsd += sellAmountUsd; // Extract affiliateBps from verification details - const verificationDetails = swap.affiliateVerificationDetails as any; + const verificationDetails = + swap.affiliateVerificationDetails as AffiliateVerificationDetails | null; const affiliateBps = verificationDetails?.affiliateBps; if (affiliateBps && sellAmountUsd > 0) { @@ -384,7 +405,8 @@ export class SwapsService { const sellAmountUsd = parseFloat( calculateUsdValue(swap.sellAmountCryptoPrecision, price), ); - const verificationDetails = swap.affiliateVerificationDetails as any; + const verificationDetails = + swap.affiliateVerificationDetails as AffiliateVerificationDetails | null; const affiliateBps = verificationDetails?.affiliateBps; if (affiliateBps && sellAmountUsd > 0) { @@ -414,6 +436,60 @@ export class SwapsService { }; } + private getDistributionCutoff(): Date { + const now = new Date(); + const cutoff = new Date( + Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 5), + ); + if (now >= cutoff) return cutoff; + return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - 1, 5)); + } + + private async resolveSwapUsdValue( + swap: { + id: string; + swapId: string; + sellAsset: unknown; + sellAmountCryptoPrecision: string; + sellAmountUsd: string | null; + createdAt: Date; + }, + priceMap: Map, + freezeCutoff: Date, + calculateUsdValue: (amount: string, price: number) => string, + ): Promise { + const sellAsset = swap.sellAsset as Asset; + const isFrozen = swap.createdAt < freezeCutoff; + + if (isFrozen && swap.sellAmountUsd) { + return parseFloat(swap.sellAmountUsd); + } + + const price = priceMap.get(sellAsset.assetId); + if (!price) { + this.logger.warn( + `No price found for asset ${sellAsset.assetId}, skipping swap ${swap.swapId}`, + ); + return null; + } + + const liveUsdValue = parseFloat( + calculateUsdValue(swap.sellAmountCryptoPrecision, price), + ); + + if (isFrozen && !swap.sellAmountUsd) { + await this.prisma.swap.update({ + where: { id: swap.id }, + data: { sellAmountUsd: liveUsdValue.toFixed(2) }, + }); + this.logger.log( + `Froze sellAmountUsd=${liveUsdValue.toFixed(2)} for swap ${swap.swapId} (pre-distribution)`, + ); + } + + return liveUsdValue; + } + async calculateAffiliateFees( affiliateAddress: string, startDate?: Date, @@ -423,7 +499,12 @@ export class SwapsService { `Calculating affiliate fees for address: ${affiliateAddress}, period: ${startDate?.toISOString()} - ${endDate?.toISOString()}`, ); - const periodWhereClause: any = { + const freezeCutoff = this.getDistributionCutoff(); + this.logger.log( + `Distribution freeze cutoff: ${freezeCutoff.toISOString()}`, + ); + + const periodWhereClause: Prisma.SwapWhereInput = { affiliateAddress, isAffiliateVerified: true, status: 'SUCCESS', @@ -436,17 +517,27 @@ export class SwapsService { }; } + const swapSelect = { + id: true, + swapId: true, + swapperName: true, + sellAsset: true, + buyAsset: true, + sellAmountCryptoBaseUnit: true, + sellAmountCryptoPrecision: true, + expectedBuyAmountCryptoBaseUnit: true, + sellAmountUsd: true, + affiliateBps: true, + origin: true, + affiliateFeeAssetId: true, + affiliateFeeAmountCryptoBaseUnit: true, + affiliateVerificationDetails: true, + createdAt: true, + } as const; + const periodSwaps = await this.prisma.swap.findMany({ where: periodWhereClause, - select: { - id: true, - swapId: true, - sellAsset: true, - sellAmountCryptoPrecision: true, - sellAmountUsd: true, - affiliateVerificationDetails: true, - createdAt: true, - }, + select: swapSelect, }); const allTimeSwaps = await this.prisma.swap.findMany({ @@ -455,25 +546,13 @@ export class SwapsService { isAffiliateVerified: true, status: 'SUCCESS', }, - select: { - id: true, - swapId: true, - sellAsset: true, - sellAmountCryptoPrecision: true, - sellAmountUsd: true, - affiliateVerificationDetails: true, - createdAt: true, - }, + select: swapSelect, }); this.logger.log( `Found ${periodSwaps.length} swaps for period, ${allTimeSwaps.length} swaps all-time for affiliate ${affiliateAddress}`, ); - let periodFeesUsd = 0; - let totalSwapVolumeUsd = 0; - const swapCount = periodSwaps.length; - const { getAssetPriceUsd, calculateUsdValue } = await import( '../utils/pricing' ); @@ -481,9 +560,13 @@ export class SwapsService { const uniqueAssets = new Map(); for (const swap of [...periodSwaps, ...allTimeSwaps]) { const sellAsset = swap.sellAsset as Asset; + const buyAsset = swap.buyAsset as Asset; if (!uniqueAssets.has(sellAsset.assetId)) { uniqueAssets.set(sellAsset.assetId, sellAsset); } + if (!uniqueAssets.has(buyAsset.assetId)) { + uniqueAssets.set(buyAsset.assetId, buyAsset); + } } const pricePromises = Array.from(uniqueAssets.values()).map( @@ -499,80 +582,180 @@ export class SwapsService { priceMap.set(assetId, price); }); - for (const swap of periodSwaps) { - const sellAsset = swap.sellAsset as Asset; + let periodCommissionUsd = 0; + let totalSwapVolumeUsd = 0; + const swapCount = periodSwaps.length; - let sellAmountUsd: number; - if (swap.sellAmountUsd) { - sellAmountUsd = parseFloat(swap.sellAmountUsd); - } else { - const price = priceMap.get(sellAsset.assetId); - if (!price) { - this.logger.warn( - `No price found for asset ${sellAsset.assetId}, skipping swap ${swap.swapId}`, - ); - continue; - } - sellAmountUsd = parseFloat( - calculateUsdValue(swap.sellAmountCryptoPrecision, price), - ); - } + for (const swap of periodSwaps) { + const sellAmountUsd = await this.resolveSwapUsdValue( + swap, + priceMap, + freezeCutoff, + calculateUsdValue, + ); + if (sellAmountUsd === null) continue; totalSwapVolumeUsd += sellAmountUsd; - const verificationDetails = swap.affiliateVerificationDetails as any; - const affiliateBps = verificationDetails?.affiliateBps; + const verificationDetails = + swap.affiliateVerificationDetails as AffiliateVerificationDetails | null; + const verifiedBps = verificationDetails?.affiliateBps; - if (affiliateBps && sellAmountUsd > 0) { - const feeUsd = (sellAmountUsd * affiliateBps) / 10000; - periodFeesUsd += feeUsd; + if (verifiedBps && sellAmountUsd > 0) { + const commissionRate = this.getAffiliateCommissionRate( + swap.origin, + verifiedBps, + ); + + const verifiedSell = + verificationDetails?.verifiedSellAmountCryptoBaseUnit; + const effectiveSellAmount = verifiedSell + ? bnOrZero(verifiedSell).lt(bnOrZero(swap.sellAmountCryptoBaseUnit)) + ? verifiedSell + : swap.sellAmountCryptoBaseUnit + : swap.sellAmountCryptoBaseUnit; + + let totalFeeUsd: number; + const feeAssetId = swap.affiliateFeeAssetId; + const feeAssetPrice = feeAssetId ? priceMap.get(feeAssetId) : null; + + if (feeAssetId && feeAssetPrice) { + const sellAssetObj = swap.sellAsset as Asset; + const buyAssetObj = swap.buyAsset as Asset; + const feeAmountBaseUnit = this.estimateAffiliateFeeAmount( + verifiedBps, + swap.swapperName, + effectiveSellAmount, + swap.expectedBuyAmountCryptoBaseUnit, + ); + const feeAssetPrecision = + feeAssetId === sellAssetObj.assetId + ? sellAssetObj.precision + : buyAssetObj.precision; + const feeAmountHuman = + parseFloat(feeAmountBaseUnit) / Math.pow(10, feeAssetPrecision); + totalFeeUsd = feeAmountHuman * feeAssetPrice; + } else { + totalFeeUsd = (sellAmountUsd * verifiedBps) / 10000; + } + + periodCommissionUsd += totalFeeUsd * commissionRate; } } - let allTimeFeesUsd = 0; + let allTimeCommissionUsd = 0; for (const swap of allTimeSwaps) { - const sellAsset = swap.sellAsset as Asset; + const sellAmountUsd = await this.resolveSwapUsdValue( + swap, + priceMap, + freezeCutoff, + calculateUsdValue, + ); + if (sellAmountUsd === null) continue; + + const verificationDetails = + swap.affiliateVerificationDetails as AffiliateVerificationDetails | null; + const verifiedBps = verificationDetails?.affiliateBps; - let sellAmountUsd: number; - if (swap.sellAmountUsd) { - sellAmountUsd = parseFloat(swap.sellAmountUsd); - } else { - const price = priceMap.get(sellAsset.assetId); - if (!price) continue; - sellAmountUsd = parseFloat( - calculateUsdValue(swap.sellAmountCryptoPrecision, price), + if (verifiedBps && sellAmountUsd > 0) { + const commissionRate = this.getAffiliateCommissionRate( + swap.origin, + verifiedBps, ); - } - const verificationDetails = swap.affiliateVerificationDetails as any; - const affiliateBps = verificationDetails?.affiliateBps; + const verifiedSell = + verificationDetails?.verifiedSellAmountCryptoBaseUnit; + const effectiveSellAmount = verifiedSell + ? bnOrZero(verifiedSell).lt(bnOrZero(swap.sellAmountCryptoBaseUnit)) + ? verifiedSell + : swap.sellAmountCryptoBaseUnit + : swap.sellAmountCryptoBaseUnit; + + let totalFeeUsd: number; + const feeAssetId = swap.affiliateFeeAssetId; + const feeAssetPrice = feeAssetId ? priceMap.get(feeAssetId) : null; + + if (feeAssetId && feeAssetPrice) { + const sellAssetObj = swap.sellAsset as Asset; + const buyAssetObj = swap.buyAsset as Asset; + const feeAmountBaseUnit = this.estimateAffiliateFeeAmount( + verifiedBps, + swap.swapperName, + effectiveSellAmount, + swap.expectedBuyAmountCryptoBaseUnit, + ); + const feeAssetPrecision = + feeAssetId === sellAssetObj.assetId + ? sellAssetObj.precision + : buyAssetObj.precision; + const feeAmountHuman = + parseFloat(feeAmountBaseUnit) / Math.pow(10, feeAssetPrecision); + totalFeeUsd = feeAmountHuman * feeAssetPrice; + } else { + totalFeeUsd = (sellAmountUsd * verifiedBps) / 10000; + } - if (affiliateBps && sellAmountUsd > 0) { - const feeUsd = (sellAmountUsd * affiliateBps) / 10000; - allTimeFeesUsd += feeUsd; + allTimeCommissionUsd += totalFeeUsd * commissionRate; } } - const periodReferrerCommissionUsd = periodFeesUsd * 0.1; - const allTimeReferrerCommissionUsd = allTimeFeesUsd * 0.1; - this.logger.log( `Affiliate fee calculation for ${affiliateAddress}: ` + - `Period: ${swapCount} swaps, $${totalSwapVolumeUsd.toFixed(2)} volume, $${periodReferrerCommissionUsd.toFixed(2)} commission | ` + - `All-time: ${allTimeSwaps.length} swaps, $${allTimeReferrerCommissionUsd.toFixed(2)} total commission`, + `Period: ${swapCount} swaps, $${totalSwapVolumeUsd.toFixed(2)} volume, $${periodCommissionUsd.toFixed(2)} commission | ` + + `All-time: ${allTimeSwaps.length} swaps, $${allTimeCommissionUsd.toFixed(2)} total commission`, ); return { affiliateAddress, swapCount, totalSwapVolumeUsd: totalSwapVolumeUsd.toFixed(2), - totalFeesCollectedUsd: allTimeReferrerCommissionUsd.toFixed(2), - referrerCommissionUsd: periodReferrerCommissionUsd.toFixed(2), + totalFeesCollectedUsd: allTimeCommissionUsd.toFixed(2), + referrerCommissionUsd: periodCommissionUsd.toFixed(2), periodStart: startDate?.toISOString(), periodEnd: endDate?.toISOString(), }; } + private static readonly API_BASE_BPS = 10; + private static readonly WEB_REVENUE_SHARE = 0.1; + + private getAffiliateCommissionRate( + origin: string | null, + verifiedBps: number, + ): number { + if (origin === 'web') { + return SwapsService.WEB_REVENUE_SHARE; + } + if (!origin || verifiedBps <= SwapsService.API_BASE_BPS) return 0; + return (verifiedBps - SwapsService.API_BASE_BPS) / verifiedBps; + } + + private estimateAffiliateFeeAmount( + affiliateBps: number, + swapperName: string, + sellAmountCryptoBaseUnit: string, + expectedBuyAmountCryptoBaseUnit: string, + ): string { + const strategy = getSwapperFeeStrategy(swapperName); + const bpsMultiplier = affiliateBps / 10000; + + switch (strategy) { + case 'sell_asset': + return bnOrZero(sellAmountCryptoBaseUnit) + .times(bpsMultiplier) + .toFixed(0); + case 'buy_asset': + return bnOrZero(expectedBuyAmountCryptoBaseUnit) + .times(bpsMultiplier) + .toFixed(0); + case 'fixed_base': + default: + return bnOrZero(sellAmountCryptoBaseUnit) + .times(bpsMultiplier) + .toFixed(0); + } + } + async pollSwapStatus(swapId: string): Promise { try { this.logger.log(`Polling status for swap: ${swapId}`); @@ -587,7 +770,7 @@ export class SwapsService { const sellAsset = swap.sellAsset as Asset; - const swapper = swappers[swap.swapperName]; + const swapper = swappers[swap.swapperName as SwapperName]; if (!swapper) { throw new Error(`Swapper not found: ${swap.swapperName}`); @@ -599,7 +782,7 @@ export class SwapsService { const status = await swapper.checkTradeStatus({ txHash: swap.sellTxHash ?? '', - chainId: sellAsset.chainId as ChainId, + chainId: sellAsset.chainId, address: swap.sellAccountId, swap: { ...swap, @@ -678,6 +861,7 @@ export class SwapsService { hasAffiliate: boolean; affiliateBps?: number; affiliateAddress?: string; + verifiedSellAmountCryptoBaseUnit?: string; } | undefined; @@ -689,6 +873,7 @@ export class SwapsService { expectedBuyAmountCryptoPrecision: swap.expectedBuyAmountCryptoPrecision, createdAt: swap.createdAt.getTime(), + sellAssetPrecision: sellAsset.precision, }; const verificationResult = @@ -708,6 +893,8 @@ export class SwapsService { hasAffiliate: verificationResult.hasAffiliate, affiliateBps: verificationResult.affiliateBps, affiliateAddress: verificationResult.affiliateAddress, + verifiedSellAmountCryptoBaseUnit: + verificationResult.verifiedSellAmountCryptoBaseUnit, }; } @@ -734,9 +921,9 @@ export class SwapsService { return { status: - status.status === 'Confirmed' + status.status === TxStatus.Confirmed ? 'SUCCESS' - : status.status === 'Failed' + : status.status === TxStatus.Failed ? 'FAILED' : 'PENDING', sellTxHash: swap.sellTxHash, diff --git a/apps/swap-service/src/utils/affiliateFeeAsset.js b/apps/swap-service/src/utils/affiliateFeeAsset.js new file mode 100644 index 0000000..83ede27 --- /dev/null +++ b/apps/swap-service/src/utils/affiliateFeeAsset.js @@ -0,0 +1,57 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.resolveAffiliateFeeAssetId = resolveAffiliateFeeAssetId; +exports.getSwapperFeeStrategy = getSwapperFeeStrategy; +exports.resolveAffiliateFeeAsset = resolveAffiliateFeeAsset; +const SWAPPER_FEE_STRATEGY = { + THORChain: 'buy_asset', + MAYAChain: 'buy_asset', + 'CoW Swap': 'buy_asset', + '0x': 'buy_asset', + Jupiter: 'buy_asset', + Chainflip: 'buy_asset', + Across: 'buy_asset', + Portals: 'buy_asset', + Bebop: 'buy_asset', + ButterSwap: 'buy_asset', + 'STON.fi': 'buy_asset', + Cetus: 'buy_asset', + 'Sun.io': 'buy_asset', + 'NEAR Intents': 'buy_asset', + AVNU: 'sell_asset', + Relay: 'fixed_base', +}; +const BASE_USDC_ASSET_ID = 'eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'; +function resolveAffiliateFeeAssetId(swapperName, sellAsset, buyAsset) { + const strategy = SWAPPER_FEE_STRATEGY[swapperName]; + if (!strategy) + return null; + switch (strategy) { + case 'buy_asset': + return buyAsset.assetId; + case 'sell_asset': + return sellAsset.assetId; + case 'fixed_base': + return BASE_USDC_ASSET_ID; + default: + return null; + } +} +function getSwapperFeeStrategy(swapperName) { + return SWAPPER_FEE_STRATEGY[swapperName] ?? null; +} +function resolveAffiliateFeeAsset(swapperName, sellAsset, buyAsset) { + const strategy = SWAPPER_FEE_STRATEGY[swapperName]; + if (!strategy) + return null; + switch (strategy) { + case 'buy_asset': + return buyAsset; + case 'sell_asset': + return sellAsset; + case 'fixed_base': + return null; + default: + return null; + } +} diff --git a/apps/swap-service/src/utils/affiliateFeeAsset.ts b/apps/swap-service/src/utils/affiliateFeeAsset.ts new file mode 100644 index 0000000..7bfca97 --- /dev/null +++ b/apps/swap-service/src/utils/affiliateFeeAsset.ts @@ -0,0 +1,71 @@ +import { Asset } from '@shapeshiftoss/types'; + +type FeeAssetStrategy = 'buy_asset' | 'sell_asset' | 'fixed_base'; + +const SWAPPER_FEE_STRATEGY: Record = { + THORChain: 'buy_asset', + MAYAChain: 'buy_asset', + 'CoW Swap': 'buy_asset', + '0x': 'buy_asset', + Jupiter: 'buy_asset', + Chainflip: 'buy_asset', + Across: 'buy_asset', + Portals: 'buy_asset', + Bebop: 'buy_asset', + ButterSwap: 'buy_asset', + 'STON.fi': 'buy_asset', + Cetus: 'buy_asset', + 'Sun.io': 'buy_asset', + 'NEAR Intents': 'buy_asset', + AVNU: 'sell_asset', + Relay: 'fixed_base', +}; + +const BASE_USDC_ASSET_ID = + 'eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'; + +export function resolveAffiliateFeeAssetId( + swapperName: string, + sellAsset: Asset, + buyAsset: Asset, +): string | null { + const strategy = SWAPPER_FEE_STRATEGY[swapperName]; + if (!strategy) return null; + + switch (strategy) { + case 'buy_asset': + return buyAsset.assetId; + case 'sell_asset': + return sellAsset.assetId; + case 'fixed_base': + return BASE_USDC_ASSET_ID; + default: + return null; + } +} + +export function getSwapperFeeStrategy( + swapperName: string, +): FeeAssetStrategy | null { + return SWAPPER_FEE_STRATEGY[swapperName] ?? null; +} + +export function resolveAffiliateFeeAsset( + swapperName: string, + sellAsset: Asset, + buyAsset: Asset, +): Asset | null { + const strategy = SWAPPER_FEE_STRATEGY[swapperName]; + if (!strategy) return null; + + switch (strategy) { + case 'buy_asset': + return buyAsset; + case 'sell_asset': + return sellAsset; + case 'fixed_base': + return null; + default: + return null; + } +} diff --git a/apps/swap-service/src/utils/pricing.js b/apps/swap-service/src/utils/pricing.js new file mode 100644 index 0000000..4b40b8a --- /dev/null +++ b/apps/swap-service/src/utils/pricing.js @@ -0,0 +1,59 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getAssetPriceUsd = getAssetPriceUsd; +exports.calculateUsdValue = calculateUsdValue; +const axios_1 = __importDefault(require("axios")); +const caip_1 = require("@shapeshiftoss/caip"); +// Simple in-memory cache +const priceCache = new Map(); +const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes +async function getAssetPriceUsd(asset) { + const cacheKey = asset.assetId; + // Check cache + const cached = priceCache.get(cacheKey); + if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) { + return cached.price; + } + try { + // Use CAIP adapters to dynamically get CoinGecko URL for any supported asset + const url = caip_1.adapters.makeCoingeckoAssetUrl(asset.assetId); + if (!url) { + console.warn(`No CoinGecko URL mapping for assetId: ${asset.assetId}`); + return null; + } + // Fetch price from CoinGecko + const { data } = await axios_1.default.get(url, { + timeout: 5000, + }); + const price = data?.market_data?.current_price?.usd || null; + if (price !== null) { + // Cache the result + priceCache.set(cacheKey, { price, timestamp: Date.now() }); + return price; + } + else { + console.warn(`No price data found for ${asset.assetId} (symbol: ${asset.symbol})`); + return null; + } + } + catch (error) { + console.error(`Failed to fetch price for ${asset.assetId}:`, error); + return null; + } +} +function calculateUsdValue(cryptoAmount, priceUsd) { + try { + const amount = parseFloat(cryptoAmount); + if (isNaN(amount)) + return '0'; + const usdValue = amount * priceUsd; + return usdValue.toFixed(2); + } + catch (error) { + console.error('Failed to calculate USD value:', error); + return '0'; + } +} diff --git a/apps/swap-service/src/utils/pricing.ts b/apps/swap-service/src/utils/pricing.ts index deb5239..256f51a 100644 --- a/apps/swap-service/src/utils/pricing.ts +++ b/apps/swap-service/src/utils/pricing.ts @@ -33,7 +33,9 @@ export async function getAssetPriceUsd(asset: Asset): Promise { } // Fetch price from CoinGecko - const { data } = await axios.get(url, { timeout: 5000 }); + const { data } = await axios.get(url, { + timeout: 5000, + }); const price = data?.market_data?.current_price?.usd || null; if (price !== null) { @@ -41,7 +43,9 @@ export async function getAssetPriceUsd(asset: Asset): Promise { priceCache.set(cacheKey, { price, timestamp: Date.now() }); return price; } else { - console.warn(`No price data found for ${asset.assetId} (symbol: ${asset.symbol})`); + console.warn( + `No price data found for ${asset.assetId} (symbol: ${asset.symbol})`, + ); return null; } } catch (error) { @@ -50,7 +54,10 @@ export async function getAssetPriceUsd(asset: Asset): Promise { } } -export function calculateUsdValue(cryptoAmount: string, priceUsd: number): string { +export function calculateUsdValue( + cryptoAmount: string, + priceUsd: number, +): string { try { const amount = parseFloat(cryptoAmount); if (isNaN(amount)) return '0'; diff --git a/apps/swap-service/src/verification/swap-verification.service.js b/apps/swap-service/src/verification/swap-verification.service.js new file mode 100644 index 0000000..f394b19 --- /dev/null +++ b/apps/swap-service/src/verification/swap-verification.service.js @@ -0,0 +1,859 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var SwapVerificationService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SwapVerificationService = void 0; +const common_1 = require("@nestjs/common"); +const axios_1 = require("@nestjs/axios"); +const rxjs_1 = require("rxjs"); +const one_click_sdk_typescript_1 = require("@defuse-protocol/one-click-sdk-typescript"); +const swapper_1 = require("@shapeshiftoss/swapper"); +const THORCHAIN_PRECISION = 8; +const thorchainToNativePrecision = (thorchainAmount, nativePrecision) => { + const diff = nativePrecision - THORCHAIN_PRECISION; + if (diff === 0) + return thorchainAmount; + if (diff > 0) + return thorchainAmount + '0'.repeat(diff); + const trimmed = thorchainAmount.slice(0, diff); + return trimmed || '0'; +}; +let SwapVerificationService = SwapVerificationService_1 = class SwapVerificationService { + constructor(httpService) { + this.httpService = httpService; + this.logger = new common_1.Logger(SwapVerificationService_1.name); + this.oneClickServiceInitialized = false; + } + initializeOneClickService(apiKey) { + if (this.oneClickServiceInitialized) + return; + const oneClickBaseUrl = 'https://1click.chaindefuser.com'; + one_click_sdk_typescript_1.OpenAPI.BASE = oneClickBaseUrl; + one_click_sdk_typescript_1.OpenAPI.TOKEN = apiKey; + this.oneClickServiceInitialized = true; + this.logger.log('OneClickService initialized'); + } + async verifySwapAffiliate(swapId, protocol, sellChainId, txHash, metadata) { + try { + this.logger.log(`Verifying affiliate for swap ${swapId} on protocol ${protocol}`); + switch (protocol.toLowerCase()) { + case 'near': + case 'nearintents': + case 'near intents': + return await this.verifyNearIntents(swapId, metadata); + case 'relay': + return await this.verifyRelay(swapId, (metadata?.relayTransactionMetadata).relayId); + case 'cow swap': + return await this.verifyCowSwap(swapId, sellChainId, txHash, metadata); + case 'portals': + return await this.verifyPortals(swapId, sellChainId, metadata); + case 'thorchain': + return await this.verifyThorchain(swapId, txHash, metadata); + case 'maya': + case 'mayachain': + return await this.verifyMaya(swapId, txHash, metadata); + case 'chainflip': + return await this.verifyChainflip(swapId, metadata); + case '0x': + case 'zrx': + return await this.verifyZrx(swapId, txHash, metadata); + case 'bebop': + return await this.verifyBebop(swapId, txHash, metadata); + default: + return { + isVerified: false, + hasAffiliate: false, + protocol, + swapId, + error: `Verification not implemented for protocol: ${protocol}`, + }; + } + } + catch (error) { + this.logger.error(`Error verifying swap ${swapId} for protocol ${protocol}:`, error); + return { + isVerified: false, + hasAffiliate: false, + protocol, + swapId, + error: error instanceof Error ? error.message : 'Unknown error', + }; + } + } + async verifyNearIntents(swapId, metadata) { + // NEAR intents uses depositAddress to query execution status + // The depositAddress is stored in nearIntentsSpecific metadata + const depositAddress = metadata?.nearIntentsSpecific?.depositAddress; + if (!depositAddress) { + return { + isVerified: false, + hasAffiliate: false, + protocol: 'near', + swapId, + error: 'Missing depositAddress in metadata.nearIntentsSpecific', + }; + } + try { + // Initialize OneClickService with API key (same approach as web) + const apiKey = process.env.VITE_NEAR_INTENTS_API_KEY; + if (!apiKey) { + this.logger.error('Missing VITE_NEAR_INTENTS_API_KEY for NEAR Intents verification'); + return { + isVerified: false, + hasAffiliate: false, + protocol: 'near', + swapId, + error: 'Missing VITE_NEAR_INTENTS_API_KEY', + }; + } + this.initializeOneClickService(apiKey); + const statusResponse = await one_click_sdk_typescript_1.OneClickService.getExecutionStatus(depositAddress); + if (!statusResponse) { + return { + isVerified: false, + hasAffiliate: false, + protocol: 'near', + swapId, + error: 'No execution status found', + }; + } + // Check if the quote request contains affiliate fees + // SDK structure: statusResponse.quoteResponse.quoteRequest + const quoteRequest = statusResponse.quoteResponse?.quoteRequest; + // Verify it's ShapeShift's affiliate + // The referral field should be 'shapeshift' from the quote request + const referral = quoteRequest?.referral; + const shapeshiftReferral = process.env.SHAPESHIFT_NEAR_REFERRAL || 'shapeshift'; + const hasShapeshiftReferral = referral?.toLowerCase() === shapeshiftReferral.toLowerCase(); + // Check if there are app fees + const appFees = quoteRequest?.appFees || []; + const hasAppFees = appFees.length > 0; + const hasShapeshiftAffiliate = hasShapeshiftReferral && hasAppFees; + // Extract fee amount if present + let affiliateBps; + if (hasAppFees && appFees[0]) { + affiliateBps = appFees[0].fee; + } + const swapDetails = statusResponse.swapDetails; + const quoteAmounts = statusResponse.quoteResponse?.quote; + let verifiedSellAmountCryptoBaseUnit; + const rawDepositedAmount = swapDetails?.depositedAmount ?? + swapDetails?.amountIn ?? + quoteAmounts?.amountIn; + if (rawDepositedAmount) { + const sellAssetPrecision = metadata?.sellAssetPrecision; + if (sellAssetPrecision && rawDepositedAmount.includes('.')) { + const [whole, frac = ''] = rawDepositedAmount.split('.'); + verifiedSellAmountCryptoBaseUnit = + whole + + frac.padEnd(sellAssetPrecision, '0').slice(0, sellAssetPrecision); + } + else { + verifiedSellAmountCryptoBaseUnit = rawDepositedAmount; + } + } + return { + isVerified: true, + hasAffiliate: hasShapeshiftAffiliate, + affiliateBps, + affiliateAddress: hasShapeshiftAffiliate + ? shapeshiftReferral + : undefined, + verifiedSellAmountCryptoBaseUnit, + protocol: 'near', + swapId, + details: { + depositAddress, + referral, + appFees, + quoteRequest, + swapDetails, + }, + }; + } + catch (error) { + this.logger.error(`Error verifying NEAR intents for swap ${swapId}:`, error); + return { + isVerified: false, + hasAffiliate: false, + protocol: 'near', + swapId, + error: error instanceof Error + ? error.message + : 'Failed to fetch NEAR intents status', + }; + } + } + async verifyRelay(swapId, txHash) { + if (!txHash) { + return { + isVerified: false, + hasAffiliate: false, + protocol: 'relay', + swapId, + error: 'Missing txHash for Relay verification', + }; + } + try { + const relayApiUrl = process.env.VITE_RELAY_API_URL || 'https://api.relay.link'; + const requestUrl = `${relayApiUrl}/requests/v2?id=${txHash}`; + const response = await (0, rxjs_1.firstValueFrom)(this.httpService.get(requestUrl)); + const requests = response.data?.requests; + if (!requests || requests.length === 0) { + return { + isVerified: false, + hasAffiliate: false, + protocol: 'relay', + swapId, + error: 'No request data found from Relay API', + }; + } + const request = requests[0]; + // Check for referrer field at top level + const referrer = request.referrer; + const shapeshiftReferrer = process.env.SHAPESHIFT_RELAY_REFERRER || 'shapeshift'; + const hasShapeshiftReferrer = referrer?.toLowerCase() === shapeshiftReferrer.toLowerCase(); + // Check for appFees or paidAppFees in the data object + const appFees = request.data?.appFees || request.data?.paidAppFees || []; + // Extract affiliate info from appFees + let affiliateBps; + let affiliateAddress; + if (appFees.length > 0) { + // Get the first app fee entry (should be ShapeShift's) + const fee = appFees[0]; + affiliateBps = fee.bps ? parseInt(fee.bps) : undefined; + affiliateAddress = fee.recipient; + } + // Verification is successful if we have shapeshift as referrer AND we have app fees + const hasShapeshiftAffiliate = hasShapeshiftReferrer && appFees.length > 0; + const verifiedSellAmountCryptoBaseUnit = request.data?.inTxs?.[0]?.data?.value?.toString() ?? + request.data?.metadata?.currencyIn?.amount?.toString() ?? + undefined; + return { + isVerified: true, + hasAffiliate: hasShapeshiftAffiliate, + affiliateBps, + affiliateAddress, + verifiedSellAmountCryptoBaseUnit, + protocol: 'relay', + swapId, + details: { + txHash, + referrer, + appFees, + request, + }, + }; + } + catch (error) { + this.logger.error(`Error verifying Relay for swap ${swapId}:`, error); + return { + isVerified: false, + hasAffiliate: false, + protocol: 'relay', + swapId, + error: error instanceof Error + ? error.message + : 'Failed to fetch Relay request data', + }; + } + } + async verifyCowSwap(swapId, sellChainId, txHash, metadata) { + // SECURITY: Always verify appData from CowSwap API using appDataHash + // to prevent users from pushing fake data to abuse the referral system + const appDataHash = metadata?.cowswapQuoteSpecific?.quote?.appDataHash; + if (!appDataHash) { + this.logger.warn(`CowSwap - Missing appDataHash for swap ${swapId}`); + return { + isVerified: false, + hasAffiliate: false, + protocol: 'cowswap', + swapId, + error: 'Missing appDataHash in metadata', + }; + } + try { + // ALWAYS fetch appData from CowSwap API to verify it's legitimate + this.logger.log(`CowSwap - Fetching appData from API using hash ${appDataHash} for swap ${swapId}`); + const cowswapApiUrl = process.env.VITE_COWSWAP_BASE_URL || 'https://api.cow.fi'; + const cowNetwork = (0, swapper_1.assertGetCowNetwork)(sellChainId); + const response = await (0, rxjs_1.firstValueFrom)(this.httpService.get(`${cowswapApiUrl}/${cowNetwork}/api/v1/app_data/${appDataHash}`)); + const decodedAppData = JSON.parse(response.data.fullAppData); + // Check if appCode is "shapeshift" + const appCode = decodedAppData?.appCode; + const shapeshiftAppCode = process.env.SHAPESHIFT_COWSWAP_APPCODE || 'shapeshift'; + const hasShapeshiftAppCode = appCode?.toLowerCase() === shapeshiftAppCode.toLowerCase(); + // Extract partner fee information from metadata.partnerFee + const partnerFee = decodedAppData?.metadata?.partnerFee; + const affiliateBps = partnerFee?.bps; + const affiliateAddress = partnerFee?.recipient; + // We have ShapeShift affiliate if appCode is shapeshift AND we have partnerFee + const hasShapeshiftAffiliate = hasShapeshiftAppCode && !!partnerFee; + let verifiedSellAmountCryptoBaseUnit; + const orderUid = txHash || metadata?.cowswapOrderUid; + if (orderUid) { + try { + const orderResponse = await (0, rxjs_1.firstValueFrom)(this.httpService.get(`${cowswapApiUrl}/${cowNetwork}/api/v1/orders/${orderUid}`)); + verifiedSellAmountCryptoBaseUnit = + orderResponse.data?.executedSellAmountBeforeFees?.toString() ?? + orderResponse.data?.executedSellAmount?.toString(); + } + catch (orderErr) { + this.logger.warn(`CowSwap - Failed to fetch order ${orderUid} for amount verification:`, orderErr); + } + } + this.logger.log(`CowSwap verification for swap ${swapId}: appCode=${appCode}, hasPartnerFee=${!!partnerFee}, bps=${affiliateBps}, verified=${hasShapeshiftAffiliate}`); + return { + isVerified: true, + hasAffiliate: hasShapeshiftAffiliate, + affiliateBps: hasShapeshiftAffiliate && affiliateBps ? affiliateBps : undefined, + affiliateAddress: hasShapeshiftAffiliate ? affiliateAddress : undefined, + verifiedSellAmountCryptoBaseUnit, + protocol: 'cowswap', + swapId, + details: { + appCode, + partnerFee, + decodedAppData, + }, + }; + } + catch (error) { + this.logger.error(`Error verifying CowSwap for swap ${swapId}:`, error); + return { + isVerified: false, + hasAffiliate: false, + protocol: 'cowswap', + swapId, + error: error instanceof Error + ? error.message + : 'Failed to decode CowSwap appData', + }; + } + } + async verifyPortals(swapId, sellChainId, metadata) { + // SECURITY: Always verify partner address from Portals API using orderId + // to prevent users from pushing fake data to abuse the referral system + // Get the orderId from the swap (stored as the quote id) + const orderId = metadata?.portalsTransactionMetadata?.orderId; + if (!orderId) { + this.logger.warn(`Portals - Missing orderId for swap ${swapId}`); + return { + isVerified: false, + hasAffiliate: false, + protocol: 'portals', + swapId, + error: 'Missing orderId in metadata', + }; + } + // Get the expected treasury address for this chain + let expectedTreasuryAddress; + try { + expectedTreasuryAddress = (0, swapper_1.getTreasuryAddressFromChainId)(sellChainId); + } + catch { + this.logger.warn(`Portals - Unsupported chain for treasury address: ${sellChainId}`); + return { + isVerified: false, + hasAffiliate: false, + protocol: 'portals', + swapId, + error: `Unsupported chain for treasury address: ${sellChainId}`, + }; + } + try { + // ALWAYS fetch order status from Portals API to verify it's legitimate + this.logger.log(`Portals - Fetching order status from API using orderId ${orderId} for swap ${swapId}`); + const portalsProxyUrl = process.env.PORTALS_PROXY_URL || + 'https://api.proxy.shapeshift.com/api/v1/portals'; + const response = await (0, rxjs_1.firstValueFrom)(this.httpService.get(`${portalsProxyUrl}/v2/portal/status?orderId=${orderId}`)); + const orderData = response.data; + this.logger.log(`Portals - Fetched and verified order from API for swap ${swapId}`); + // Get partner from the API response context + const partner = orderData?.context?.partner; + if (!partner) { + this.logger.warn(`Portals - No partner found in API response for swap ${swapId}`); + return { + isVerified: false, + hasAffiliate: false, + protocol: 'portals', + swapId, + error: 'No partner found in Portals API response', + }; + } + // Verify partner matches the expected treasury address (case-insensitive for EVM addresses) + const hasShapeshiftAffiliate = partner.toLowerCase() === expectedTreasuryAddress.toLowerCase(); + // Extract fee information from the order context + // feeAmount and feeAmountUsd are in the context + const feeAmount = orderData?.context?.feeAmount; + const feeAmountUsd = orderData?.context?.feeAmountUsd; + const verifiedSellAmountCryptoBaseUnit = orderData?.context?.inputAmount?.toString() ?? undefined; + this.logger.log(`Portals verification for swap ${swapId}: partner=${partner}, expectedTreasury=${expectedTreasuryAddress}, verified=${hasShapeshiftAffiliate}, feeAmount=${feeAmount}`); + return { + isVerified: true, + hasAffiliate: hasShapeshiftAffiliate, + affiliateBps: metadata?.affiliateBps + ? parseInt(metadata.affiliateBps) + : undefined, + affiliateAddress: hasShapeshiftAffiliate + ? expectedTreasuryAddress + : undefined, + verifiedSellAmountCryptoBaseUnit, + protocol: 'portals', + swapId, + details: { + orderId, + partner, + expectedTreasuryAddress, + sellChainId, + feeAmount, + feeAmountUsd, + orderData, + }, + }; + } + catch (error) { + this.logger.error(`Error verifying Portals for swap ${swapId}:`, error); + return { + isVerified: false, + hasAffiliate: false, + protocol: 'portals', + swapId, + error: error instanceof Error + ? error.message + : 'Failed to verify Portals order', + }; + } + } + async verifyThorchain(swapId, txHash, metadata) { + if (!txHash) { + return { + isVerified: false, + hasAffiliate: false, + protocol: 'thorchain', + swapId, + error: 'Missing txHash for Thorchain verification', + }; + } + try { + // SECURITY: Query Thorchain node API to verify memo contains affiliate info + const nodeUrl = process.env.VITE_THORCHAIN_NODE_URL || + 'https://thornode.ninerealms.com'; + const txUrl = `${nodeUrl}/thorchain/tx/${txHash}`; + this.logger.log(`Thorchain - Fetching tx from node API: ${txUrl}`); + const response = await (0, rxjs_1.firstValueFrom)(this.httpService.get(txUrl)); + const observedTx = response.data?.observed_tx; + if (!observedTx || !observedTx.tx) { + return { + isVerified: false, + hasAffiliate: false, + protocol: 'thorchain', + swapId, + error: 'No observed transaction found', + }; + } + const memo = observedTx.tx.memo; + if (!memo) { + return { + isVerified: false, + hasAffiliate: false, + protocol: 'thorchain', + swapId, + error: 'No memo found in transaction', + }; + } + // Parse memo format: =:r:thor1dz68dtlzrxnjflha9vvs7yt7p77mqdnf5yugww:131082237:ss:0 + // The affiliate code is after the 4th colon, followed by fee in bps + const shapeshiftAffiliate = process.env.SHAPESHIFT_THORCHAIN_AFFILIATE || 'ss'; + const memoPattern = new RegExp(`:${shapeshiftAffiliate}:(\\d+)`, 'i'); + const memoMatch = memo.match(memoPattern); + const hasShapeshiftAffiliate = !!memoMatch; + const affiliateBps = memoMatch ? parseInt(memoMatch[1]) : undefined; + const coins = observedTx.tx.coins; + const sellAssetPrecision = metadata?.sellAssetPrecision ?? + THORCHAIN_PRECISION; + const firstCoinAmount = coins?.[0]?.amount; + const verifiedSellAmountCryptoBaseUnit = firstCoinAmount + ? thorchainToNativePrecision(firstCoinAmount, sellAssetPrecision) + : undefined; + this.logger.log(`Thorchain verification for swap ${swapId}: memo=${memo}, affiliate=${shapeshiftAffiliate}, hasAffiliate=${hasShapeshiftAffiliate}, bps=${affiliateBps}`); + return { + isVerified: true, + hasAffiliate: hasShapeshiftAffiliate, + affiliateBps: hasShapeshiftAffiliate && affiliateBps ? affiliateBps : undefined, + affiliateAddress: hasShapeshiftAffiliate + ? shapeshiftAffiliate + : undefined, + verifiedSellAmountCryptoBaseUnit, + protocol: 'thorchain', + swapId, + details: { + txHash, + memo, + observedTx, + }, + }; + } + catch (error) { + this.logger.error(`Error verifying Thorchain for swap ${swapId}:`, error); + return { + isVerified: false, + hasAffiliate: false, + protocol: 'thorchain', + swapId, + error: error instanceof Error + ? error.message + : 'Failed to fetch Thorchain data from node', + }; + } + } + async verifyMaya(swapId, txHash, metadata) { + if (!txHash) { + return { + isVerified: false, + hasAffiliate: false, + protocol: 'maya', + swapId, + error: 'Missing txHash for Maya verification', + }; + } + try { + // SECURITY: Query Maya node API to verify memo contains affiliate info + const nodeUrl = process.env.VITE_MAYACHAIN_NODE_URL || + 'https://mayanode.mayachain.info'; + const txUrl = `${nodeUrl}/mayachain/tx/${txHash}`; + this.logger.log(`Maya - Fetching tx from node API: ${txUrl}`); + const response = await (0, rxjs_1.firstValueFrom)(this.httpService.get(txUrl)); + const observedTx = response.data?.observed_tx; + if (!observedTx || !observedTx.tx) { + return { + isVerified: false, + hasAffiliate: false, + protocol: 'maya', + swapId, + error: 'No observed transaction found', + }; + } + const memo = observedTx.tx.memo; + if (!memo) { + return { + isVerified: false, + hasAffiliate: false, + protocol: 'maya', + swapId, + error: 'No memo found in transaction', + }; + } + // Parse memo format: =:r:maya1dz68dtlzrxnjflha9vvs7yt7p77mqdnf5yugww:131082237:ss:0 + // The affiliate code is after the 4th colon, followed by fee in bps + const shapeshiftAffiliate = process.env.SHAPESHIFT_MAYA_AFFILIATE || 'ssmaya'; + const memoPattern = new RegExp(`:${shapeshiftAffiliate}:(\\d+)`, 'i'); + const memoMatch = memo.match(memoPattern); + const hasShapeshiftAffiliate = !!memoMatch; + const affiliateBps = memoMatch ? parseInt(memoMatch[1]) : undefined; + const coins = observedTx.tx.coins; + const sellAssetPrecision = metadata?.sellAssetPrecision ?? + THORCHAIN_PRECISION; + const firstCoinAmount = coins?.[0]?.amount; + const verifiedSellAmountCryptoBaseUnit = firstCoinAmount + ? thorchainToNativePrecision(firstCoinAmount, sellAssetPrecision) + : undefined; + this.logger.log(`Maya verification for swap ${swapId}: memo=${memo}, affiliate=${shapeshiftAffiliate}, hasAffiliate=${hasShapeshiftAffiliate}, bps=${affiliateBps}`); + return { + isVerified: true, + hasAffiliate: hasShapeshiftAffiliate, + affiliateBps: hasShapeshiftAffiliate && affiliateBps ? affiliateBps : undefined, + affiliateAddress: hasShapeshiftAffiliate + ? shapeshiftAffiliate + : undefined, + verifiedSellAmountCryptoBaseUnit, + protocol: 'maya', + swapId, + details: { + txHash, + memo, + observedTx, + }, + }; + } + catch (error) { + this.logger.error(`Error verifying Maya for swap ${swapId}:`, error); + return { + isVerified: false, + hasAffiliate: false, + protocol: 'maya', + swapId, + error: error instanceof Error + ? error.message + : 'Failed to fetch Maya data from node', + }; + } + } + async verifyChainflip(swapId, metadata) { + const chainflipSwapId = metadata?.chainflipSwapId; + if (!chainflipSwapId) { + return { + isVerified: false, + hasAffiliate: false, + protocol: 'chainflip', + swapId, + error: 'Missing chainflipSwapId in metadata', + }; + } + try { + const chainflipApiUrl = process.env.VITE_CHAINFLIP_API_URL || 'https://api.chainflip.io'; + const statusUrl = `${chainflipApiUrl}/swaps/${chainflipSwapId}`; + const headers = {}; + const apiKey = process.env.VITE_CHAINFLIP_API_KEY; + if (apiKey) { + headers['Authorization'] = `Bearer ${apiKey}`; + } + const response = await (0, rxjs_1.firstValueFrom)(this.httpService.get(statusUrl, { headers })); + const swapData = response.data; + if (!swapData) { + return { + isVerified: false, + hasAffiliate: false, + protocol: 'chainflip', + swapId, + error: 'No swap data found from Chainflip API', + }; + } + // Check for affiliate information in the swap data + const affiliate = swapData.affiliate || swapData.affiliateName; + const affiliateBps = swapData.affiliateBps || swapData.affiliateFee; + const shapeshiftAffiliate = process.env.SHAPESHIFT_CHAINFLIP_AFFILIATE || 'shapeshift'; + const hasShapeshiftAffiliate = affiliate?.toLowerCase() === shapeshiftAffiliate.toLowerCase(); + const verifiedSellAmountCryptoBaseUnit = (swapData.depositAmount ?? + swapData.ingressAmount ?? + swapData.sourceAmount)?.toString(); + return { + isVerified: true, + hasAffiliate: hasShapeshiftAffiliate, + affiliateBps: hasShapeshiftAffiliate && affiliateBps + ? parseInt(String(affiliateBps)) + : undefined, + affiliateAddress: hasShapeshiftAffiliate + ? shapeshiftAffiliate + : undefined, + verifiedSellAmountCryptoBaseUnit, + protocol: 'chainflip', + swapId, + details: { + chainflipSwapId, + affiliate, + swapData, + }, + }; + } + catch (error) { + this.logger.error(`Error verifying Chainflip for swap ${swapId}:`, error); + return { + isVerified: false, + hasAffiliate: false, + protocol: 'chainflip', + swapId, + error: error instanceof Error + ? error.message + : 'Failed to fetch Chainflip swap data', + }; + } + } + async verifyZrx(swapId, txHash, metadata) { + const tradeHash = txHash || + metadata?.tradeHash || + metadata?.txHash; + if (!tradeHash) { + return { + isVerified: false, + hasAffiliate: false, + protocol: '0x', + swapId, + error: 'Missing tradeHash in metadata', + }; + } + try { + // Use 0x Trade Analytics API via ShapeShift proxy to verify the trade + const zrxProxyUrl = process.env.ZRX_PROXY_URL || + 'https://api.proxy.shapeshift.com/api/v1/zrx'; + const requestUrl = `${zrxProxyUrl}/trade-analytics/swap`; + const response = await (0, rxjs_1.firstValueFrom)(this.httpService.get(requestUrl)); + const trades = Array.isArray(response.data) + ? response.data + : response.data?.trades || response.data?.results || []; + const trade = trades.find((t) => t.txHash?.toLowerCase() === tradeHash.toLowerCase() || + t.transactionHash?.toLowerCase() === tradeHash.toLowerCase()); + if (!trade) { + return { + isVerified: false, + hasAffiliate: false, + protocol: '0x', + swapId, + error: `Trade not found in 0x analytics (searched ${trades.length} trades)`, + }; + } + // Check for ShapeShift's partner/integrator name + // The field could be integratorId, integratorName, or affiliateName + const integratorId = trade.integratorId || trade.integratorName || trade.affiliateName; + const shapeshiftIntegrator = process.env.SHAPESHIFT_0X_INTEGRATOR || 'ShapeShift'; + const hasShapeshiftAffiliate = integratorId?.toLowerCase() === shapeshiftIntegrator.toLowerCase(); + // Extract fee information + // The fee could be in integratorFee, affiliateFee, or partnerFee fields + // Note: 0x fees are typically in decimal format (e.g., 0.0015 for 15 bps) + const integratorFee = trade.integratorFee || trade.affiliateFee || trade.partnerFee; + let affiliateBps; + if (integratorFee) { + // Convert decimal fee to basis points (e.g., 0.0015 -> 15 bps) + affiliateBps = parseFloat(integratorFee) * 10000; + } + const verifiedSellAmountCryptoBaseUnit = (trade.sellAmount ?? + trade.inputTokenAmount ?? + trade.amount)?.toString(); + return { + isVerified: true, + hasAffiliate: hasShapeshiftAffiliate, + affiliateBps, + affiliateAddress: hasShapeshiftAffiliate + ? shapeshiftIntegrator + : undefined, + verifiedSellAmountCryptoBaseUnit, + protocol: '0x', + swapId, + details: { + tradeHash, + integratorId, + integratorFee, + trade, + }, + }; + } + catch (error) { + this.logger.error(`Error verifying 0x for swap ${swapId}:`, error); + return { + isVerified: false, + hasAffiliate: false, + protocol: '0x', + swapId, + error: error instanceof Error ? error.message : 'Failed to verify 0x trade', + }; + } + } + async verifyBebop(swapId, txHash, metadata) { + if (!txHash) { + return { + isVerified: false, + hasAffiliate: false, + protocol: 'bebop', + swapId, + error: 'Missing txHash for Bebop verification', + }; + } + try { + // Use trade history API to find the trade by source filter + const bebopApiUrl = process.env.VITE_BEBOP_API_URL || 'https://api.bebop.xyz'; + const shapeshiftSource = process.env.SHAPESHIFT_BEBOP_SOURCE || 'shapeshift'; + // Get swap timestamp to create time range (swap createdAt +/- 1 hour) + const swapTimestamp = metadata?.createdAt || Date.now(); + const oneHour = 60 * 60 * 1000; + const startNano = (swapTimestamp - oneHour) * 1000000; // Convert to nanoseconds + const endNano = (swapTimestamp + oneHour) * 1000000; + // Query trade history with source filter and time range + const queryParams = new URLSearchParams({ + start: startNano.toString(), + end: endNano.toString(), + source: shapeshiftSource, + }); + // Need source-auth header with API key to query by source + const apiKey = process.env.VITE_BEBOP_API_KEY; + if (!apiKey) { + this.logger.error('Missing VITE_BEBOP_API_KEY for Bebop verification'); + return { + isVerified: false, + hasAffiliate: false, + protocol: 'bebop', + swapId, + error: 'Missing VITE_BEBOP_API_KEY for source authentication', + }; + } + const headers = { + 'source-auth': apiKey, + }; + const requestUrl = `${bebopApiUrl}/history/v2/trades?${queryParams.toString()}`; + // Log request details + this.logger.log(`Bebop API Request - URL: ${requestUrl}`); + this.logger.log(`Bebop API Request - Params: ${JSON.stringify({ + start: startNano.toString(), + end: endNano.toString(), + source: shapeshiftSource, + swapTimestamp: new Date(swapTimestamp).toISOString(), + })}`); + this.logger.log(`Bebop API Request - Headers: { 'source-auth': '${apiKey.substring(0, 8)}...' }`); + this.logger.log(`Bebop API Request - Looking for txHash: ${txHash}`); + const response = await (0, rxjs_1.firstValueFrom)(this.httpService.get(requestUrl, { headers })); + this.logger.log(`Bebop API Response - Status: ${response.status}`); + this.logger.log(`Bebop API Response - Data: ${JSON.stringify(response.data)}`); + const trades = response.data?.results || []; + this.logger.log(`Bebop API Response - Found ${trades.length} trades`); + const trade = trades.find((t) => t.txHash?.toLowerCase() === txHash.toLowerCase()); + if (!trade) { + return { + isVerified: false, + hasAffiliate: false, + protocol: 'bebop', + swapId, + error: 'Trade not found in Bebop history', + }; + } + // Since we filtered by source=shapeshift, finding the trade means it was made through ShapeShift + const hasShapeshiftAffiliate = true; + // Extract partner fee from the response (partnerFeeBps is in basis points) + const partnerFeeBps = trade.partnerFeeBps; + const affiliateBps = partnerFeeBps != null ? Number(partnerFeeBps) : undefined; + const sellTokenEntries = trade.sellTokens + ? Object.values(trade.sellTokens) + : []; + const verifiedSellAmountCryptoBaseUnit = sellTokenEntries[0]?.amount?.toString() ?? undefined; + this.logger.log(`Bebop verification: trade found, partnerFeeBps=${partnerFeeBps}, hasAffiliate=true`); + return { + isVerified: true, + hasAffiliate: hasShapeshiftAffiliate, + affiliateBps, + affiliateAddress: shapeshiftSource, + verifiedSellAmountCryptoBaseUnit, + protocol: 'bebop', + swapId, + details: { + txHash, + trade, + partnerFeeBps, + partnerFeeNative: trade.partnerFeeNative, + }, + }; + } + catch (error) { + this.logger.error(`Error verifying Bebop for swap ${swapId}:`, error); + return { + isVerified: false, + hasAffiliate: false, + protocol: 'bebop', + swapId, + error: error instanceof Error + ? error.message + : 'Failed to verify Bebop trade', + }; + } + } +}; +exports.SwapVerificationService = SwapVerificationService; +exports.SwapVerificationService = SwapVerificationService = SwapVerificationService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [axios_1.HttpService]) +], SwapVerificationService); diff --git a/apps/swap-service/src/verification/swap-verification.service.ts b/apps/swap-service/src/verification/swap-verification.service.ts index 7529eee..bfe2ab8 100644 --- a/apps/swap-service/src/verification/swap-verification.service.ts +++ b/apps/swap-service/src/verification/swap-verification.service.ts @@ -2,8 +2,123 @@ import { Injectable, Logger } from '@nestjs/common'; import { SwapVerificationResult } from '@shapeshift/shared-types'; import { HttpService } from '@nestjs/axios'; import { firstValueFrom } from 'rxjs'; -import { OneClickService, OpenAPI } from '@defuse-protocol/one-click-sdk-typescript'; -import { assertGetCowNetwork, getTreasuryAddressFromChainId } from '@shapeshiftoss/swapper'; +import { + OneClickService, + OpenAPI, +} from '@defuse-protocol/one-click-sdk-typescript'; +import { + assertGetCowNetwork, + getTreasuryAddressFromChainId, +} from '@shapeshiftoss/swapper'; + +interface ThorchainMayaTxResponse { + observed_tx?: { + tx?: { + memo?: string; + coins?: Array<{ amount?: string }>; + }; + }; +} + +interface RelayAppFee { + bps?: string; + recipient?: string; +} + +interface RelayRequest { + referrer?: string; + data?: { + appFees?: RelayAppFee[]; + paidAppFees?: RelayAppFee[]; + inTxs?: Array<{ data?: { value?: string } }>; + metadata?: { currencyIn?: { amount?: string } }; + }; +} + +interface RelayRequestsResponse { + requests?: RelayRequest[]; +} + +interface CowSwapAppDataResponse { + fullAppData: string; +} + +interface CowSwapDecodedAppData { + appCode?: string; + metadata?: { + partnerFee?: { + bps?: number; + recipient?: string; + }; + }; +} + +interface CowSwapOrderResponse { + executedSellAmountBeforeFees?: string; + executedSellAmount?: string; +} + +interface PortalsOrderResponse { + context?: { + partner?: string; + inputAmount?: string; + feeAmount?: string; + feeAmountUsd?: string; + }; +} + +interface ChainflipSwapResponse { + affiliate?: string; + affiliateName?: string; + affiliateBps?: string; + affiliateFee?: string; + depositAmount?: string; + ingressAmount?: string; + sourceAmount?: string; +} + +interface ZrxTrade { + txHash?: string; + transactionHash?: string; + integratorId?: string; + integratorName?: string; + affiliateName?: string; + integratorFee?: string; + affiliateFee?: string; + partnerFee?: string; + sellAmount?: string; + inputTokenAmount?: string; + amount?: string; +} + +interface ZrxApiResponse { + trades?: ZrxTrade[]; + results?: ZrxTrade[]; +} + +interface BebopTrade { + txHash?: string; + partnerFeeBps?: number; + sellTokens?: Record; + partnerFeeNative?: string; +} + +interface BebopTradesResponse { + results?: BebopTrade[]; +} + +const THORCHAIN_PRECISION = 8; + +const thorchainToNativePrecision = ( + thorchainAmount: string, + nativePrecision: number, +): string => { + const diff = nativePrecision - THORCHAIN_PRECISION; + if (diff === 0) return thorchainAmount; + if (diff > 0) return thorchainAmount + '0'.repeat(diff); + const trimmed = thorchainAmount.slice(0, diff); + return trimmed || '0'; +}; @Injectable() export class SwapVerificationService { @@ -31,7 +146,9 @@ export class SwapVerificationService { metadata?: Record, ): Promise { try { - this.logger.log(`Verifying affiliate for swap ${swapId} on protocol ${protocol}`); + this.logger.log( + `Verifying affiliate for swap ${swapId} on protocol ${protocol}`, + ); switch (protocol.toLowerCase()) { case 'near': @@ -40,20 +157,28 @@ export class SwapVerificationService { return await this.verifyNearIntents(swapId, metadata); case 'relay': - return await this.verifyRelay(swapId, metadata.relayTransactionMetadata.relayId); + return await this.verifyRelay( + swapId, + (metadata?.relayTransactionMetadata as { relayId: string }).relayId, + ); case 'cow swap': - return await this.verifyCowSwap(swapId, sellChainId, metadata); + return await this.verifyCowSwap( + swapId, + sellChainId, + txHash, + metadata, + ); case 'portals': return await this.verifyPortals(swapId, sellChainId, metadata); case 'thorchain': - return await this.verifyThorchain(swapId, txHash); + return await this.verifyThorchain(swapId, txHash, metadata); case 'maya': case 'mayachain': - return await this.verifyMaya(swapId, txHash); + return await this.verifyMaya(swapId, txHash, metadata); case 'chainflip': return await this.verifyChainflip(swapId, metadata); @@ -75,7 +200,10 @@ export class SwapVerificationService { }; } } catch (error) { - this.logger.error(`Error verifying swap ${swapId} for protocol ${protocol}:`, error); + this.logger.error( + `Error verifying swap ${swapId} for protocol ${protocol}:`, + error, + ); return { isVerified: false, hasAffiliate: false, @@ -93,7 +221,9 @@ export class SwapVerificationService { // NEAR intents uses depositAddress to query execution status // The depositAddress is stored in nearIntentsSpecific metadata - const depositAddress = metadata?.nearIntentsSpecific?.depositAddress; + const depositAddress = ( + metadata?.nearIntentsSpecific as { depositAddress?: string } | undefined + )?.depositAddress; if (!depositAddress) { return { @@ -109,7 +239,9 @@ export class SwapVerificationService { // Initialize OneClickService with API key (same approach as web) const apiKey = process.env.VITE_NEAR_INTENTS_API_KEY; if (!apiKey) { - this.logger.error('Missing VITE_NEAR_INTENTS_API_KEY for NEAR Intents verification'); + this.logger.error( + 'Missing VITE_NEAR_INTENTS_API_KEY for NEAR Intents verification', + ); return { isVerified: false, hasAffiliate: false, @@ -121,7 +253,8 @@ export class SwapVerificationService { this.initializeOneClickService(apiKey); - const statusResponse = await OneClickService.getExecutionStatus(depositAddress); + const statusResponse = + await OneClickService.getExecutionStatus(depositAddress); if (!statusResponse) { return { @@ -140,8 +273,10 @@ export class SwapVerificationService { // Verify it's ShapeShift's affiliate // The referral field should be 'shapeshift' from the quote request const referral = quoteRequest?.referral; - const shapeshiftReferral = process.env.SHAPESHIFT_NEAR_REFERRAL || 'shapeshift'; - const hasShapeshiftReferral = referral?.toLowerCase() === shapeshiftReferral.toLowerCase(); + const shapeshiftReferral = + process.env.SHAPESHIFT_NEAR_REFERRAL || 'shapeshift'; + const hasShapeshiftReferral = + referral?.toLowerCase() === shapeshiftReferral.toLowerCase(); // Check if there are app fees const appFees = quoteRequest?.appFees || []; @@ -155,11 +290,40 @@ export class SwapVerificationService { affiliateBps = appFees[0].fee; } + const swapDetails = ( + statusResponse as unknown as { + swapDetails?: { depositedAmount?: string; amountIn?: string }; + } + ).swapDetails; + const quoteAmounts = statusResponse.quoteResponse?.quote; + let verifiedSellAmountCryptoBaseUnit: string | undefined; + + const rawDepositedAmount: string | undefined = + swapDetails?.depositedAmount ?? + swapDetails?.amountIn ?? + quoteAmounts?.amountIn; + if (rawDepositedAmount) { + const sellAssetPrecision = metadata?.sellAssetPrecision as + | number + | undefined; + if (sellAssetPrecision && rawDepositedAmount.includes('.')) { + const [whole, frac = ''] = rawDepositedAmount.split('.'); + verifiedSellAmountCryptoBaseUnit = + whole + + frac.padEnd(sellAssetPrecision, '0').slice(0, sellAssetPrecision); + } else { + verifiedSellAmountCryptoBaseUnit = rawDepositedAmount; + } + } + return { isVerified: true, hasAffiliate: hasShapeshiftAffiliate, affiliateBps, - affiliateAddress: hasShapeshiftAffiliate ? shapeshiftReferral : undefined, + affiliateAddress: hasShapeshiftAffiliate + ? shapeshiftReferral + : undefined, + verifiedSellAmountCryptoBaseUnit, protocol: 'near', swapId, details: { @@ -167,16 +331,23 @@ export class SwapVerificationService { referral, appFees, quoteRequest, + swapDetails, }, }; } catch (error) { - this.logger.error(`Error verifying NEAR intents for swap ${swapId}:`, error); + this.logger.error( + `Error verifying NEAR intents for swap ${swapId}:`, + error, + ); return { isVerified: false, hasAffiliate: false, protocol: 'near', swapId, - error: error instanceof Error ? error.message : 'Failed to fetch NEAR intents status', + error: + error instanceof Error + ? error.message + : 'Failed to fetch NEAR intents status', }; } } @@ -196,14 +367,14 @@ export class SwapVerificationService { } try { - const relayApiUrl = process.env.VITE_RELAY_API_URL || 'https://api.relay.link'; + const relayApiUrl = + process.env.VITE_RELAY_API_URL || 'https://api.relay.link'; const requestUrl = `${relayApiUrl}/requests/v2?id=${txHash}`; const response = await firstValueFrom( - this.httpService.get(requestUrl), + this.httpService.get(requestUrl), ); - // Response has a requests array const requests = response.data?.requests; if (!requests || requests.length === 0) { @@ -220,8 +391,10 @@ export class SwapVerificationService { // Check for referrer field at top level const referrer = request.referrer; - const shapeshiftReferrer = process.env.SHAPESHIFT_RELAY_REFERRER || 'shapeshift'; - const hasShapeshiftReferrer = referrer?.toLowerCase() === shapeshiftReferrer.toLowerCase(); + const shapeshiftReferrer = + process.env.SHAPESHIFT_RELAY_REFERRER || 'shapeshift'; + const hasShapeshiftReferrer = + referrer?.toLowerCase() === shapeshiftReferrer.toLowerCase(); // Check for appFees or paidAppFees in the data object const appFees = request.data?.appFees || request.data?.paidAppFees || []; @@ -238,13 +411,20 @@ export class SwapVerificationService { } // Verification is successful if we have shapeshift as referrer AND we have app fees - const hasShapeshiftAffiliate = hasShapeshiftReferrer && appFees.length > 0; + const hasShapeshiftAffiliate = + hasShapeshiftReferrer && appFees.length > 0; + + const verifiedSellAmountCryptoBaseUnit = + request.data?.inTxs?.[0]?.data?.value?.toString() ?? + request.data?.metadata?.currencyIn?.amount?.toString() ?? + undefined; return { isVerified: true, hasAffiliate: hasShapeshiftAffiliate, affiliateBps, affiliateAddress, + verifiedSellAmountCryptoBaseUnit, protocol: 'relay', swapId, details: { @@ -261,7 +441,10 @@ export class SwapVerificationService { hasAffiliate: false, protocol: 'relay', swapId, - error: error instanceof Error ? error.message : 'Failed to fetch Relay request data', + error: + error instanceof Error + ? error.message + : 'Failed to fetch Relay request data', }; } } @@ -269,11 +452,16 @@ export class SwapVerificationService { private async verifyCowSwap( swapId: string, sellChainId: string, + txHash?: string, metadata?: Record, ): Promise { // SECURITY: Always verify appData from CowSwap API using appDataHash // to prevent users from pushing fake data to abuse the referral system - const appDataHash = metadata?.cowswapQuoteSpecific?.quote?.appDataHash; + const appDataHash = ( + metadata?.cowswapQuoteSpecific as + | { quote?: { appDataHash?: string } } + | undefined + )?.quote?.appDataHash; if (!appDataHash) { this.logger.warn(`CowSwap - Missing appDataHash for swap ${swapId}`); @@ -288,19 +476,28 @@ export class SwapVerificationService { try { // ALWAYS fetch appData from CowSwap API to verify it's legitimate - this.logger.log(`CowSwap - Fetching appData from API using hash ${appDataHash} for swap ${swapId}`); - const cowswapApiUrl = process.env.VITE_COWSWAP_BASE_URL || 'https://api.cow.fi'; + this.logger.log( + `CowSwap - Fetching appData from API using hash ${appDataHash} for swap ${swapId}`, + ); + const cowswapApiUrl = + process.env.VITE_COWSWAP_BASE_URL || 'https://api.cow.fi'; const cowNetwork = assertGetCowNetwork(sellChainId); const response = await firstValueFrom( - this.httpService.get(`${cowswapApiUrl}/${cowNetwork}/api/v1/app_data/${appDataHash}`), + this.httpService.get( + `${cowswapApiUrl}/${cowNetwork}/api/v1/app_data/${appDataHash}`, + ), ); - const decodedAppData = JSON.parse(response.data.fullAppData); + const decodedAppData = JSON.parse( + response.data.fullAppData, + ) as CowSwapDecodedAppData; // Check if appCode is "shapeshift" const appCode = decodedAppData?.appCode; - const shapeshiftAppCode = process.env.SHAPESHIFT_COWSWAP_APPCODE || 'shapeshift'; - const hasShapeshiftAppCode = appCode?.toLowerCase() === shapeshiftAppCode.toLowerCase(); + const shapeshiftAppCode = + process.env.SHAPESHIFT_COWSWAP_APPCODE || 'shapeshift'; + const hasShapeshiftAppCode = + appCode?.toLowerCase() === shapeshiftAppCode.toLowerCase(); // Extract partner fee information from metadata.partnerFee const partnerFee = decodedAppData?.metadata?.partnerFee; @@ -310,6 +507,27 @@ export class SwapVerificationService { // We have ShapeShift affiliate if appCode is shapeshift AND we have partnerFee const hasShapeshiftAffiliate = hasShapeshiftAppCode && !!partnerFee; + let verifiedSellAmountCryptoBaseUnit: string | undefined; + const orderUid = + txHash || (metadata?.cowswapOrderUid as string | undefined); + if (orderUid) { + try { + const orderResponse = await firstValueFrom( + this.httpService.get( + `${cowswapApiUrl}/${cowNetwork}/api/v1/orders/${orderUid}`, + ), + ); + verifiedSellAmountCryptoBaseUnit = + orderResponse.data?.executedSellAmountBeforeFees?.toString() ?? + orderResponse.data?.executedSellAmount?.toString(); + } catch (orderErr) { + this.logger.warn( + `CowSwap - Failed to fetch order ${orderUid} for amount verification:`, + orderErr, + ); + } + } + this.logger.log( `CowSwap verification for swap ${swapId}: appCode=${appCode}, hasPartnerFee=${!!partnerFee}, bps=${affiliateBps}, verified=${hasShapeshiftAffiliate}`, ); @@ -317,8 +535,10 @@ export class SwapVerificationService { return { isVerified: true, hasAffiliate: hasShapeshiftAffiliate, - affiliateBps: hasShapeshiftAffiliate && affiliateBps ? affiliateBps : undefined, + affiliateBps: + hasShapeshiftAffiliate && affiliateBps ? affiliateBps : undefined, affiliateAddress: hasShapeshiftAffiliate ? affiliateAddress : undefined, + verifiedSellAmountCryptoBaseUnit, protocol: 'cowswap', swapId, details: { @@ -334,7 +554,10 @@ export class SwapVerificationService { hasAffiliate: false, protocol: 'cowswap', swapId, - error: error instanceof Error ? error.message : 'Failed to decode CowSwap appData', + error: + error instanceof Error + ? error.message + : 'Failed to decode CowSwap appData', }; } } @@ -348,7 +571,9 @@ export class SwapVerificationService { // to prevent users from pushing fake data to abuse the referral system // Get the orderId from the swap (stored as the quote id) - const orderId = metadata?.portalsTransactionMetadata?.orderId; + const orderId = ( + metadata?.portalsTransactionMetadata as { orderId?: string } | undefined + )?.orderId; if (!orderId) { this.logger.warn(`Portals - Missing orderId for swap ${swapId}`); @@ -365,8 +590,10 @@ export class SwapVerificationService { let expectedTreasuryAddress: string; try { expectedTreasuryAddress = getTreasuryAddressFromChainId(sellChainId); - } catch (error) { - this.logger.warn(`Portals - Unsupported chain for treasury address: ${sellChainId}`); + } catch { + this.logger.warn( + `Portals - Unsupported chain for treasury address: ${sellChainId}`, + ); return { isVerified: false, hasAffiliate: false, @@ -378,20 +605,30 @@ export class SwapVerificationService { try { // ALWAYS fetch order status from Portals API to verify it's legitimate - this.logger.log(`Portals - Fetching order status from API using orderId ${orderId} for swap ${swapId}`); - const portalsProxyUrl = process.env.PORTALS_PROXY_URL || 'https://api.proxy.shapeshift.com/api/v1/portals'; + this.logger.log( + `Portals - Fetching order status from API using orderId ${orderId} for swap ${swapId}`, + ); + const portalsProxyUrl = + process.env.PORTALS_PROXY_URL || + 'https://api.proxy.shapeshift.com/api/v1/portals'; const response = await firstValueFrom( - this.httpService.get(`${portalsProxyUrl}/v2/portal/status?orderId=${orderId}`), + this.httpService.get( + `${portalsProxyUrl}/v2/portal/status?orderId=${orderId}`, + ), ); const orderData = response.data; - this.logger.log(`Portals - Fetched and verified order from API for swap ${swapId}`); + this.logger.log( + `Portals - Fetched and verified order from API for swap ${swapId}`, + ); // Get partner from the API response context const partner = orderData?.context?.partner; if (!partner) { - this.logger.warn(`Portals - No partner found in API response for swap ${swapId}`); + this.logger.warn( + `Portals - No partner found in API response for swap ${swapId}`, + ); return { isVerified: false, hasAffiliate: false, @@ -402,13 +639,17 @@ export class SwapVerificationService { } // Verify partner matches the expected treasury address (case-insensitive for EVM addresses) - const hasShapeshiftAffiliate = partner.toLowerCase() === expectedTreasuryAddress.toLowerCase(); + const hasShapeshiftAffiliate = + partner.toLowerCase() === expectedTreasuryAddress.toLowerCase(); // Extract fee information from the order context // feeAmount and feeAmountUsd are in the context const feeAmount = orderData?.context?.feeAmount; const feeAmountUsd = orderData?.context?.feeAmountUsd; + const verifiedSellAmountCryptoBaseUnit = + orderData?.context?.inputAmount?.toString() ?? undefined; + this.logger.log( `Portals verification for swap ${swapId}: partner=${partner}, expectedTreasury=${expectedTreasuryAddress}, verified=${hasShapeshiftAffiliate}, feeAmount=${feeAmount}`, ); @@ -416,8 +657,13 @@ export class SwapVerificationService { return { isVerified: true, hasAffiliate: hasShapeshiftAffiliate, - affiliateBps: metadata?.affiliateBps ? parseInt(metadata.affiliateBps) : undefined, - affiliateAddress: hasShapeshiftAffiliate ? expectedTreasuryAddress : undefined, + affiliateBps: metadata?.affiliateBps + ? parseInt(metadata.affiliateBps as string) + : undefined, + affiliateAddress: hasShapeshiftAffiliate + ? expectedTreasuryAddress + : undefined, + verifiedSellAmountCryptoBaseUnit, protocol: 'portals', swapId, details: { @@ -437,7 +683,10 @@ export class SwapVerificationService { hasAffiliate: false, protocol: 'portals', swapId, - error: error instanceof Error ? error.message : 'Failed to verify Portals order', + error: + error instanceof Error + ? error.message + : 'Failed to verify Portals order', }; } } @@ -445,6 +694,7 @@ export class SwapVerificationService { private async verifyThorchain( swapId: string, txHash?: string, + metadata?: Record, ): Promise { if (!txHash) { return { @@ -458,13 +708,15 @@ export class SwapVerificationService { try { // SECURITY: Query Thorchain node API to verify memo contains affiliate info - const nodeUrl = process.env.VITE_THORCHAIN_NODE_URL || 'https://thornode.ninerealms.com'; + const nodeUrl = + process.env.VITE_THORCHAIN_NODE_URL || + 'https://thornode.ninerealms.com'; const txUrl = `${nodeUrl}/thorchain/tx/${txHash}`; this.logger.log(`Thorchain - Fetching tx from node API: ${txUrl}`); const response = await firstValueFrom( - this.httpService.get(txUrl), + this.httpService.get(txUrl), ); const observedTx = response.data?.observed_tx; @@ -479,7 +731,7 @@ export class SwapVerificationService { }; } - const memo = observedTx.tx.memo; + const memo: string | undefined = observedTx.tx.memo; if (!memo) { return { isVerified: false, @@ -492,13 +744,23 @@ export class SwapVerificationService { // Parse memo format: =:r:thor1dz68dtlzrxnjflha9vvs7yt7p77mqdnf5yugww:131082237:ss:0 // The affiliate code is after the 4th colon, followed by fee in bps - const shapeshiftAffiliate = process.env.SHAPESHIFT_THORCHAIN_AFFILIATE || 'ss'; + const shapeshiftAffiliate = + process.env.SHAPESHIFT_THORCHAIN_AFFILIATE || 'ss'; const memoPattern = new RegExp(`:${shapeshiftAffiliate}:(\\d+)`, 'i'); const memoMatch = memo.match(memoPattern); const hasShapeshiftAffiliate = !!memoMatch; const affiliateBps = memoMatch ? parseInt(memoMatch[1]) : undefined; + const coins = observedTx.tx.coins; + const sellAssetPrecision = + (metadata?.sellAssetPrecision as number | undefined) ?? + THORCHAIN_PRECISION; + const firstCoinAmount = coins?.[0]?.amount; + const verifiedSellAmountCryptoBaseUnit = firstCoinAmount + ? thorchainToNativePrecision(firstCoinAmount, sellAssetPrecision) + : undefined; + this.logger.log( `Thorchain verification for swap ${swapId}: memo=${memo}, affiliate=${shapeshiftAffiliate}, hasAffiliate=${hasShapeshiftAffiliate}, bps=${affiliateBps}`, ); @@ -506,8 +768,12 @@ export class SwapVerificationService { return { isVerified: true, hasAffiliate: hasShapeshiftAffiliate, - affiliateBps: hasShapeshiftAffiliate && affiliateBps ? affiliateBps : undefined, - affiliateAddress: hasShapeshiftAffiliate ? shapeshiftAffiliate : undefined, + affiliateBps: + hasShapeshiftAffiliate && affiliateBps ? affiliateBps : undefined, + affiliateAddress: hasShapeshiftAffiliate + ? shapeshiftAffiliate + : undefined, + verifiedSellAmountCryptoBaseUnit, protocol: 'thorchain', swapId, details: { @@ -523,7 +789,10 @@ export class SwapVerificationService { hasAffiliate: false, protocol: 'thorchain', swapId, - error: error instanceof Error ? error.message : 'Failed to fetch Thorchain data from node', + error: + error instanceof Error + ? error.message + : 'Failed to fetch Thorchain data from node', }; } } @@ -531,6 +800,7 @@ export class SwapVerificationService { private async verifyMaya( swapId: string, txHash?: string, + metadata?: Record, ): Promise { if (!txHash) { return { @@ -544,13 +814,15 @@ export class SwapVerificationService { try { // SECURITY: Query Maya node API to verify memo contains affiliate info - const nodeUrl = process.env.VITE_MAYACHAIN_NODE_URL || 'https://mayanode.mayachain.info'; + const nodeUrl = + process.env.VITE_MAYACHAIN_NODE_URL || + 'https://mayanode.mayachain.info'; const txUrl = `${nodeUrl}/mayachain/tx/${txHash}`; this.logger.log(`Maya - Fetching tx from node API: ${txUrl}`); const response = await firstValueFrom( - this.httpService.get(txUrl), + this.httpService.get(txUrl), ); const observedTx = response.data?.observed_tx; @@ -565,7 +837,7 @@ export class SwapVerificationService { }; } - const memo = observedTx.tx.memo; + const memo: string | undefined = observedTx.tx.memo; if (!memo) { return { isVerified: false, @@ -578,13 +850,23 @@ export class SwapVerificationService { // Parse memo format: =:r:maya1dz68dtlzrxnjflha9vvs7yt7p77mqdnf5yugww:131082237:ss:0 // The affiliate code is after the 4th colon, followed by fee in bps - const shapeshiftAffiliate = process.env.SHAPESHIFT_MAYA_AFFILIATE || 'ssmaya'; + const shapeshiftAffiliate = + process.env.SHAPESHIFT_MAYA_AFFILIATE || 'ssmaya'; const memoPattern = new RegExp(`:${shapeshiftAffiliate}:(\\d+)`, 'i'); const memoMatch = memo.match(memoPattern); const hasShapeshiftAffiliate = !!memoMatch; const affiliateBps = memoMatch ? parseInt(memoMatch[1]) : undefined; + const coins = observedTx.tx.coins; + const sellAssetPrecision = + (metadata?.sellAssetPrecision as number | undefined) ?? + THORCHAIN_PRECISION; + const firstCoinAmount = coins?.[0]?.amount; + const verifiedSellAmountCryptoBaseUnit = firstCoinAmount + ? thorchainToNativePrecision(firstCoinAmount, sellAssetPrecision) + : undefined; + this.logger.log( `Maya verification for swap ${swapId}: memo=${memo}, affiliate=${shapeshiftAffiliate}, hasAffiliate=${hasShapeshiftAffiliate}, bps=${affiliateBps}`, ); @@ -592,8 +874,12 @@ export class SwapVerificationService { return { isVerified: true, hasAffiliate: hasShapeshiftAffiliate, - affiliateBps: hasShapeshiftAffiliate && affiliateBps ? affiliateBps : undefined, - affiliateAddress: hasShapeshiftAffiliate ? shapeshiftAffiliate : undefined, + affiliateBps: + hasShapeshiftAffiliate && affiliateBps ? affiliateBps : undefined, + affiliateAddress: hasShapeshiftAffiliate + ? shapeshiftAffiliate + : undefined, + verifiedSellAmountCryptoBaseUnit, protocol: 'maya', swapId, details: { @@ -609,7 +895,10 @@ export class SwapVerificationService { hasAffiliate: false, protocol: 'maya', swapId, - error: error instanceof Error ? error.message : 'Failed to fetch Maya data from node', + error: + error instanceof Error + ? error.message + : 'Failed to fetch Maya data from node', }; } } @@ -618,7 +907,7 @@ export class SwapVerificationService { swapId: string, metadata?: Record, ): Promise { - const chainflipSwapId = metadata?.chainflipSwapId; + const chainflipSwapId = metadata?.chainflipSwapId as string | undefined; if (!chainflipSwapId) { return { @@ -631,7 +920,8 @@ export class SwapVerificationService { } try { - const chainflipApiUrl = process.env.VITE_CHAINFLIP_API_URL || 'https://api.chainflip.io'; + const chainflipApiUrl = + process.env.VITE_CHAINFLIP_API_URL || 'https://api.chainflip.io'; const statusUrl = `${chainflipApiUrl}/swaps/${chainflipSwapId}`; const headers: Record = {}; @@ -641,7 +931,7 @@ export class SwapVerificationService { } const response = await firstValueFrom( - this.httpService.get(statusUrl, { headers }), + this.httpService.get(statusUrl, { headers }), ); const swapData = response.data; @@ -660,14 +950,28 @@ export class SwapVerificationService { const affiliate = swapData.affiliate || swapData.affiliateName; const affiliateBps = swapData.affiliateBps || swapData.affiliateFee; - const shapeshiftAffiliate = process.env.SHAPESHIFT_CHAINFLIP_AFFILIATE || 'shapeshift'; - const hasShapeshiftAffiliate = affiliate?.toLowerCase() === shapeshiftAffiliate.toLowerCase(); + const shapeshiftAffiliate = + process.env.SHAPESHIFT_CHAINFLIP_AFFILIATE || 'shapeshift'; + const hasShapeshiftAffiliate = + affiliate?.toLowerCase() === shapeshiftAffiliate.toLowerCase(); + + const verifiedSellAmountCryptoBaseUnit = ( + swapData.depositAmount ?? + swapData.ingressAmount ?? + swapData.sourceAmount + )?.toString(); return { isVerified: true, hasAffiliate: hasShapeshiftAffiliate, - affiliateBps: hasShapeshiftAffiliate && affiliateBps ? parseInt(affiliateBps) : undefined, - affiliateAddress: hasShapeshiftAffiliate ? shapeshiftAffiliate : undefined, + affiliateBps: + hasShapeshiftAffiliate && affiliateBps + ? parseInt(String(affiliateBps)) + : undefined, + affiliateAddress: hasShapeshiftAffiliate + ? shapeshiftAffiliate + : undefined, + verifiedSellAmountCryptoBaseUnit, protocol: 'chainflip', swapId, details: { @@ -683,7 +987,10 @@ export class SwapVerificationService { hasAffiliate: false, protocol: 'chainflip', swapId, - error: error instanceof Error ? error.message : 'Failed to fetch Chainflip swap data', + error: + error instanceof Error + ? error.message + : 'Failed to fetch Chainflip swap data', }; } } @@ -693,7 +1000,10 @@ export class SwapVerificationService { txHash?: string, metadata?: Record, ): Promise { - const tradeHash = txHash || metadata?.tradeHash || metadata?.txHash; + const tradeHash = + txHash || + (metadata?.tradeHash as string | undefined) || + (metadata?.txHash as string | undefined); if (!tradeHash) { return { @@ -707,20 +1017,23 @@ export class SwapVerificationService { try { // Use 0x Trade Analytics API via ShapeShift proxy to verify the trade - const zrxProxyUrl = process.env.ZRX_PROXY_URL || 'https://api.proxy.shapeshift.com/api/v1/zrx'; + const zrxProxyUrl = + process.env.ZRX_PROXY_URL || + 'https://api.proxy.shapeshift.com/api/v1/zrx'; const requestUrl = `${zrxProxyUrl}/trade-analytics/swap`; const response = await firstValueFrom( - this.httpService.get(requestUrl), + this.httpService.get(requestUrl), ); - // Response could be an array of trades or have a trades/results field - const trades = Array.isArray(response.data) ? response.data : (response.data?.trades || response.data?.results || []); + const trades: ZrxTrade[] = Array.isArray(response.data) + ? response.data + : response.data?.trades || response.data?.results || []; - // Find trade matching our txHash - const trade = trades.find((t: any) => - t.txHash?.toLowerCase() === tradeHash.toLowerCase() || - t.transactionHash?.toLowerCase() === tradeHash.toLowerCase() + const trade = trades.find( + (t: ZrxTrade) => + t.txHash?.toLowerCase() === tradeHash.toLowerCase() || + t.transactionHash?.toLowerCase() === tradeHash.toLowerCase(), ); if (!trade) { @@ -735,14 +1048,18 @@ export class SwapVerificationService { // Check for ShapeShift's partner/integrator name // The field could be integratorId, integratorName, or affiliateName - const integratorId = trade.integratorId || trade.integratorName || trade.affiliateName; - const shapeshiftIntegrator = process.env.SHAPESHIFT_0X_INTEGRATOR || 'ShapeShift'; - const hasShapeshiftAffiliate = integratorId?.toLowerCase() === shapeshiftIntegrator.toLowerCase(); + const integratorId = + trade.integratorId || trade.integratorName || trade.affiliateName; + const shapeshiftIntegrator = + process.env.SHAPESHIFT_0X_INTEGRATOR || 'ShapeShift'; + const hasShapeshiftAffiliate = + integratorId?.toLowerCase() === shapeshiftIntegrator.toLowerCase(); // Extract fee information // The fee could be in integratorFee, affiliateFee, or partnerFee fields // Note: 0x fees are typically in decimal format (e.g., 0.0015 for 15 bps) - const integratorFee = trade.integratorFee || trade.affiliateFee || trade.partnerFee; + const integratorFee = + trade.integratorFee || trade.affiliateFee || trade.partnerFee; let affiliateBps: number | undefined; if (integratorFee) { @@ -750,11 +1067,20 @@ export class SwapVerificationService { affiliateBps = parseFloat(integratorFee) * 10000; } + const verifiedSellAmountCryptoBaseUnit = ( + trade.sellAmount ?? + trade.inputTokenAmount ?? + trade.amount + )?.toString(); + return { isVerified: true, hasAffiliate: hasShapeshiftAffiliate, affiliateBps, - affiliateAddress: hasShapeshiftAffiliate ? shapeshiftIntegrator : undefined, + affiliateAddress: hasShapeshiftAffiliate + ? shapeshiftIntegrator + : undefined, + verifiedSellAmountCryptoBaseUnit, protocol: '0x', swapId, details: { @@ -771,7 +1097,8 @@ export class SwapVerificationService { hasAffiliate: false, protocol: '0x', swapId, - error: error instanceof Error ? error.message : 'Failed to verify 0x trade', + error: + error instanceof Error ? error.message : 'Failed to verify 0x trade', }; } } @@ -793,11 +1120,14 @@ export class SwapVerificationService { try { // Use trade history API to find the trade by source filter - const bebopApiUrl = process.env.VITE_BEBOP_API_URL || 'https://api.bebop.xyz'; - const shapeshiftSource = process.env.SHAPESHIFT_BEBOP_SOURCE || 'shapeshift'; + const bebopApiUrl = + process.env.VITE_BEBOP_API_URL || 'https://api.bebop.xyz'; + const shapeshiftSource = + process.env.SHAPESHIFT_BEBOP_SOURCE || 'shapeshift'; // Get swap timestamp to create time range (swap createdAt +/- 1 hour) - const swapTimestamp = metadata?.createdAt || Date.now(); + const swapTimestamp = + (metadata?.createdAt as number | undefined) || Date.now(); const oneHour = 60 * 60 * 1000; const startNano = (swapTimestamp - oneHour) * 1_000_000; // Convert to nanoseconds const endNano = (swapTimestamp + oneHour) * 1_000_000; @@ -830,28 +1160,34 @@ export class SwapVerificationService { // Log request details this.logger.log(`Bebop API Request - URL: ${requestUrl}`); - this.logger.log(`Bebop API Request - Params: ${JSON.stringify({ - start: startNano.toString(), - end: endNano.toString(), - source: shapeshiftSource, - swapTimestamp: new Date(swapTimestamp).toISOString(), - })}`); - this.logger.log(`Bebop API Request - Headers: { 'source-auth': '${apiKey.substring(0, 8)}...' }`); + this.logger.log( + `Bebop API Request - Params: ${JSON.stringify({ + start: startNano.toString(), + end: endNano.toString(), + source: shapeshiftSource, + swapTimestamp: new Date(swapTimestamp).toISOString(), + })}`, + ); + this.logger.log( + `Bebop API Request - Headers: { 'source-auth': '${apiKey.substring(0, 8)}...' }`, + ); this.logger.log(`Bebop API Request - Looking for txHash: ${txHash}`); const response = await firstValueFrom( - this.httpService.get(requestUrl, { headers }), + this.httpService.get(requestUrl, { headers }), ); - // Log response this.logger.log(`Bebop API Response - Status: ${response.status}`); - this.logger.log(`Bebop API Response - Data: ${JSON.stringify(response.data)}`); + this.logger.log( + `Bebop API Response - Data: ${JSON.stringify(response.data)}`, + ); const trades = response.data?.results || []; this.logger.log(`Bebop API Response - Found ${trades.length} trades`); - // Find trade matching our txHash - const trade = trades.find((t: any) => t.txHash?.toLowerCase() === txHash.toLowerCase()); + const trade = trades.find( + (t: BebopTrade) => t.txHash?.toLowerCase() === txHash.toLowerCase(), + ); if (!trade) { return { @@ -868,7 +1204,14 @@ export class SwapVerificationService { // Extract partner fee from the response (partnerFeeBps is in basis points) const partnerFeeBps = trade.partnerFeeBps; - const affiliateBps = partnerFeeBps ? parseFloat(partnerFeeBps) : undefined; + const affiliateBps = + partnerFeeBps != null ? Number(partnerFeeBps) : undefined; + + const sellTokenEntries = trade.sellTokens + ? Object.values(trade.sellTokens) + : []; + const verifiedSellAmountCryptoBaseUnit = + sellTokenEntries[0]?.amount?.toString() ?? undefined; this.logger.log( `Bebop verification: trade found, partnerFeeBps=${partnerFeeBps}, hasAffiliate=true`, @@ -879,6 +1222,7 @@ export class SwapVerificationService { hasAffiliate: hasShapeshiftAffiliate, affiliateBps, affiliateAddress: shapeshiftSource, + verifiedSellAmountCryptoBaseUnit, protocol: 'bebop', swapId, details: { @@ -895,7 +1239,10 @@ export class SwapVerificationService { hasAffiliate: false, protocol: 'bebop', swapId, - error: error instanceof Error ? error.message : 'Failed to verify Bebop trade', + error: + error instanceof Error + ? error.message + : 'Failed to verify Bebop trade', }; } } diff --git a/apps/swap-service/src/websocket/websocket.gateway.js b/apps/swap-service/src/websocket/websocket.gateway.js new file mode 100644 index 0000000..7a2bb90 --- /dev/null +++ b/apps/swap-service/src/websocket/websocket.gateway.js @@ -0,0 +1,80 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +var WebsocketGateway_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.WebsocketGateway = void 0; +const websockets_1 = require("@nestjs/websockets"); +const socket_io_1 = require("socket.io"); +const common_1 = require("@nestjs/common"); +const swaps_service_1 = require("../swaps/swaps.service"); +let WebsocketGateway = WebsocketGateway_1 = class WebsocketGateway { + constructor(swapsService) { + this.swapsService = swapsService; + this.logger = new common_1.Logger(WebsocketGateway_1.name); + this.connectedClients = new Map(); + } + handleConnection(client) { + this.logger.log(`Client connected: ${client.id}`); + } + handleDisconnect(client) { + this.logger.log(`Client disconnected: ${client.id}`); + if (client.userId) { + this.connectedClients.delete(client.userId); + } + } + async handleGetSwaps(data, client) { + if (!client.userId) { + return { error: 'Not authenticated' }; + } + try { + const swaps = await this.swapsService.getSwapsByUser(client.userId, data.limit || 50); + return { success: true, swaps }; + } + catch (error) { + this.logger.error('Failed to get swaps', error); + return { error: 'Failed to get swaps' }; + } + } + sendSwapUpdateToUser(userId, swap) { + const client = this.connectedClients.get(userId); + if (client) { + client.emit('swapUpdate', swap); + } + this.server.to(`user:${userId}`).emit('swapUpdate', swap); + } + broadcastToAll(event, data) { + this.server.emit(event, data); + } +}; +exports.WebsocketGateway = WebsocketGateway; +__decorate([ + (0, websockets_1.WebSocketServer)(), + __metadata("design:type", socket_io_1.Server) +], WebsocketGateway.prototype, "server", void 0); +__decorate([ + (0, websockets_1.SubscribeMessage)('getSwaps'), + __param(0, (0, websockets_1.MessageBody)()), + __param(1, (0, websockets_1.ConnectedSocket)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", Promise) +], WebsocketGateway.prototype, "handleGetSwaps", null); +exports.WebsocketGateway = WebsocketGateway = WebsocketGateway_1 = __decorate([ + (0, websockets_1.WebSocketGateway)({ + cors: { + origin: '*', + }, + }), + __metadata("design:paramtypes", [swaps_service_1.SwapsService]) +], WebsocketGateway); diff --git a/apps/swap-service/src/websocket/websocket.gateway.ts b/apps/swap-service/src/websocket/websocket.gateway.ts index 8368a6b..a8de37b 100644 --- a/apps/swap-service/src/websocket/websocket.gateway.ts +++ b/apps/swap-service/src/websocket/websocket.gateway.ts @@ -21,7 +21,9 @@ interface AuthenticatedSocket extends Socket { origin: '*', }, }) -export class WebsocketGateway implements OnGatewayConnection, OnGatewayDisconnect { +export class WebsocketGateway + implements OnGatewayConnection, OnGatewayDisconnect +{ @WebSocketServer() server: Server; @@ -62,25 +64,28 @@ export class WebsocketGateway implements OnGatewayConnection, OnGatewayDisconnec } } - async sendSwapUpdateToUser(userId: string, swap: { - id: string; - swapId: string; - status: string; - sellAsset: Asset; - buyAsset: Asset; - sellAmountCryptoBaseUnit: string; - expectedBuyAmountCryptoBaseUnit: string; - sellAccountId: string; - buyAccountId?: string; - sellTxHash?: string; - buyTxHash?: string; - statusMessage?: string; - }) { + sendSwapUpdateToUser( + userId: string, + swap: { + id: string; + swapId: string; + status: string; + sellAsset: Asset; + buyAsset: Asset; + sellAmountCryptoBaseUnit: string; + expectedBuyAmountCryptoBaseUnit: string; + sellAccountId: string; + buyAccountId?: string; + sellTxHash?: string; + buyTxHash?: string; + statusMessage?: string; + }, + ) { const client = this.connectedClients.get(userId); if (client) { client.emit('swapUpdate', swap); } - + this.server.to(`user:${userId}`).emit('swapUpdate', swap); } diff --git a/apps/user-service/src/app.module.js b/apps/user-service/src/app.module.js new file mode 100644 index 0000000..5956f4b --- /dev/null +++ b/apps/user-service/src/app.module.js @@ -0,0 +1,31 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AppModule = void 0; +const common_1 = require("@nestjs/common"); +const config_1 = require("@nestjs/config"); +const users_controller_1 = require("./users/users.controller"); +const users_service_1 = require("./users/users.service"); +const referral_controller_1 = require("./referral/referral.controller"); +const referral_service_1 = require("./referral/referral.service"); +const prisma_service_1 = require("./prisma/prisma.service"); +let AppModule = class AppModule { +}; +exports.AppModule = AppModule; +exports.AppModule = AppModule = __decorate([ + (0, common_1.Module)({ + imports: [ + config_1.ConfigModule.forRoot({ + isGlobal: true, + envFilePath: '../../.env', + }), + ], + controllers: [users_controller_1.UsersController, referral_controller_1.ReferralController], + providers: [users_service_1.UsersService, referral_service_1.ReferralService, prisma_service_1.PrismaService], + }) +], AppModule); diff --git a/apps/user-service/src/main.js b/apps/user-service/src/main.js new file mode 100644 index 0000000..f3ffdcc --- /dev/null +++ b/apps/user-service/src/main.js @@ -0,0 +1,22 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const core_1 = require("@nestjs/core"); +const app_module_1 = require("./app.module"); +async function bootstrap() { + const app = await core_1.NestFactory.create(app_module_1.AppModule); + // Enable CORS + app.enableCors({ + origin: process.env.ALLOWED_ORIGINS?.split(',') || [ + 'http://localhost:3000', + /\.shapeshift\.com$/, + ], + credentials: true, + }); + app.getHttpAdapter().get('/health', (_, res) => { + res.status(200).json({ status: 'ok' }); + }); + const port = process.env.PORT || 3000; + await app.listen(port); + console.log(`User service is running on: http://localhost:${port}`); +} +bootstrap(); diff --git a/apps/user-service/src/prisma/prisma.service.js b/apps/user-service/src/prisma/prisma.service.js new file mode 100644 index 0000000..2b91c98 --- /dev/null +++ b/apps/user-service/src/prisma/prisma.service.js @@ -0,0 +1,23 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PrismaService = void 0; +const common_1 = require("@nestjs/common"); +const client_1 = require("@prisma/client"); +let PrismaService = class PrismaService extends client_1.PrismaClient { + async onModuleInit() { + await this.$connect(); + } + async onModuleDestroy() { + await this.$disconnect(); + } +}; +exports.PrismaService = PrismaService; +exports.PrismaService = PrismaService = __decorate([ + (0, common_1.Injectable)() +], PrismaService); diff --git a/apps/user-service/src/prisma/prisma.service.ts b/apps/user-service/src/prisma/prisma.service.ts index bb6565f..7ffd32d 100644 --- a/apps/user-service/src/prisma/prisma.service.ts +++ b/apps/user-service/src/prisma/prisma.service.ts @@ -2,7 +2,10 @@ import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; @Injectable() -export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { +export class PrismaService + extends PrismaClient + implements OnModuleInit, OnModuleDestroy +{ async onModuleInit() { await this.$connect(); } diff --git a/apps/user-service/src/referral/referral.controller.js b/apps/user-service/src/referral/referral.controller.js new file mode 100644 index 0000000..56a7dec --- /dev/null +++ b/apps/user-service/src/referral/referral.controller.js @@ -0,0 +1,135 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ReferralController = void 0; +const common_1 = require("@nestjs/common"); +const referral_service_1 = require("./referral.service"); +const shared_utils_1 = require("@shapeshift/shared-utils"); +let ReferralController = class ReferralController { + constructor(referralService) { + this.referralService = referralService; + } + async createReferralCode(data) { + if (!(0, shared_utils_1.isValidAccountId)(data.ownerAddress)) { + throw new Error('Invalid account ID'); + } + const hashedOwnerAddress = (0, shared_utils_1.hashAccountId)(data.ownerAddress); + const expiresAt = data.expiresAt ? new Date(data.expiresAt) : undefined; + return this.referralService.createReferralCode({ + code: data.code, + ownerAddress: hashedOwnerAddress, + maxUses: data.maxUses, + expiresAt, + }); + } + async useReferralCode(data) { + return this.referralService.useReferralCode(data); + } + async getAllReferralCodes(limit) { + return this.referralService.getAllReferralCodes(limit ? parseInt(limit) : 50); + } + async getReferralCodeByCode(code) { + return this.referralService.getReferralCodeByCode(code); + } + async getReferralCodesByOwner(ownerAddress) { + if (!(0, shared_utils_1.isValidAccountId)(ownerAddress)) { + throw new Error('Invalid account ID'); + } + const hashedOwnerAddress = (0, shared_utils_1.hashAccountId)(ownerAddress); + return this.referralService.getReferralCodesByOwner(hashedOwnerAddress); + } + async getReferralUsageByAddress(refereeAddress) { + return this.referralService.getReferralUsageByAddress(refereeAddress); + } + async deactivateReferralCode(code, data) { + if (!(0, shared_utils_1.isValidAccountId)(data.ownerAddress)) { + throw new Error('Invalid account ID'); + } + const hashedOwnerAddress = (0, shared_utils_1.hashAccountId)(data.ownerAddress); + return this.referralService.deactivateReferralCode(code, hashedOwnerAddress); + } + async getReferralStats(ownerAddress, startDate, endDate) { + if (!(0, shared_utils_1.isValidAccountId)(ownerAddress)) { + throw new Error('Invalid account ID'); + } + const hashedOwnerAddress = (0, shared_utils_1.hashAccountId)(ownerAddress); + const start = startDate ? new Date(startDate) : undefined; + const end = endDate ? new Date(endDate) : undefined; + return this.referralService.getReferralStatsByOwner(hashedOwnerAddress, start, end); + } +}; +exports.ReferralController = ReferralController; +__decorate([ + (0, common_1.Post)('codes'), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], ReferralController.prototype, "createReferralCode", null); +__decorate([ + (0, common_1.Post)('use'), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], ReferralController.prototype, "useReferralCode", null); +__decorate([ + (0, common_1.Get)('codes'), + __param(0, (0, common_1.Query)('limit')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", Promise) +], ReferralController.prototype, "getAllReferralCodes", null); +__decorate([ + (0, common_1.Get)('codes/:code'), + __param(0, (0, common_1.Param)('code')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", Promise) +], ReferralController.prototype, "getReferralCodeByCode", null); +__decorate([ + (0, common_1.Get)('owner/:ownerAddress'), + __param(0, (0, common_1.Param)('ownerAddress')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", Promise) +], ReferralController.prototype, "getReferralCodesByOwner", null); +__decorate([ + (0, common_1.Get)('usage/:refereeAddress'), + __param(0, (0, common_1.Param)('refereeAddress')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", Promise) +], ReferralController.prototype, "getReferralUsageByAddress", null); +__decorate([ + (0, common_1.Put)('codes/:code/deactivate'), + __param(0, (0, common_1.Param)('code')), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String, Object]), + __metadata("design:returntype", Promise) +], ReferralController.prototype, "deactivateReferralCode", null); +__decorate([ + (0, common_1.Get)('stats/:ownerAddress'), + __param(0, (0, common_1.Param)('ownerAddress')), + __param(1, (0, common_1.Query)('startDate')), + __param(2, (0, common_1.Query)('endDate')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String, String, String]), + __metadata("design:returntype", Promise) +], ReferralController.prototype, "getReferralStats", null); +exports.ReferralController = ReferralController = __decorate([ + (0, common_1.Controller)('referrals'), + __metadata("design:paramtypes", [referral_service_1.ReferralService]) +], ReferralController); diff --git a/apps/user-service/src/referral/referral.controller.ts b/apps/user-service/src/referral/referral.controller.ts index ce01aa4..cbb3a07 100644 --- a/apps/user-service/src/referral/referral.controller.ts +++ b/apps/user-service/src/referral/referral.controller.ts @@ -44,7 +44,9 @@ export class ReferralController { @Get('codes') async getAllReferralCodes(@Query('limit') limit?: string) { - return this.referralService.getAllReferralCodes(limit ? parseInt(limit) : 50); + return this.referralService.getAllReferralCodes( + limit ? parseInt(limit) : 50, + ); } @Get('codes/:code') @@ -62,7 +64,9 @@ export class ReferralController { } @Get('usage/:refereeAddress') - async getReferralUsageByAddress(@Param('refereeAddress') refereeAddress: string) { + async getReferralUsageByAddress( + @Param('refereeAddress') refereeAddress: string, + ) { return this.referralService.getReferralUsageByAddress(refereeAddress); } @@ -75,7 +79,10 @@ export class ReferralController { throw new Error('Invalid account ID'); } const hashedOwnerAddress = hashAccountId(data.ownerAddress); - return this.referralService.deactivateReferralCode(code, hashedOwnerAddress); + return this.referralService.deactivateReferralCode( + code, + hashedOwnerAddress, + ); } @Get('stats/:ownerAddress') @@ -90,6 +97,10 @@ export class ReferralController { const hashedOwnerAddress = hashAccountId(ownerAddress); const start = startDate ? new Date(startDate) : undefined; const end = endDate ? new Date(endDate) : undefined; - return this.referralService.getReferralStatsByOwner(hashedOwnerAddress, start, end); + return this.referralService.getReferralStatsByOwner( + hashedOwnerAddress, + start, + end, + ); } } diff --git a/apps/user-service/src/referral/referral.service.js b/apps/user-service/src/referral/referral.service.js new file mode 100644 index 0000000..36ba984 --- /dev/null +++ b/apps/user-service/src/referral/referral.service.js @@ -0,0 +1,244 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var ReferralService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ReferralService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../prisma/prisma.service"); +const shared_utils_1 = require("@shapeshift/shared-utils"); +let ReferralService = ReferralService_1 = class ReferralService { + constructor(prisma) { + this.prisma = prisma; + this.logger = new common_1.Logger(ReferralService_1.name); + this.swapServiceClient = new shared_utils_1.SwapServiceClient(); + } + async createReferralCode(data) { + try { + const existingCode = await this.prisma.referralCode.findUnique({ + where: { code: data.code }, + }); + if (existingCode) { + throw new common_1.BadRequestException('Referral code already exists'); + } + const referralCode = await this.prisma.referralCode.create({ + data: { + code: data.code, + ownerAddress: data.ownerAddress, + maxUses: data.maxUses, + expiresAt: data.expiresAt, + }, + }); + this.logger.log(`Created referral code: ${data.code} for address ${data.ownerAddress}`); + return referralCode; + } + catch (error) { + this.logger.error('Failed to create referral code', error); + throw error; + } + } + async useReferralCode(data) { + try { + const referralCode = await this.prisma.referralCode.findUnique({ + where: { code: data.code }, + include: { + _count: { + select: { usages: true }, + }, + }, + }); + if (!referralCode) { + throw new common_1.NotFoundException('Referral code not found'); + } + if (!referralCode.isActive) { + throw new common_1.BadRequestException('Referral code is inactive'); + } + if (referralCode.expiresAt && referralCode.expiresAt < new Date()) { + throw new common_1.BadRequestException('Referral code has expired'); + } + if (referralCode.maxUses && + referralCode._count.usages >= referralCode.maxUses) { + throw new common_1.BadRequestException('Referral code has reached maximum uses'); + } + if (referralCode.ownerAddress === data.refereeAddress) { + throw new common_1.BadRequestException('Cannot use your own referral code'); + } + const existingUsage = await this.prisma.referralUsage.findUnique({ + where: { refereeAddress: data.refereeAddress }, + }); + if (existingUsage) { + throw new common_1.BadRequestException('Address has already used a referral code'); + } + const usage = await this.prisma.referralUsage.create({ + data: { + referralCode: data.code, + refereeAddress: data.refereeAddress, + }, + }); + this.logger.log(`Referral code ${data.code} used by ${data.refereeAddress}`); + return usage; + } + catch (error) { + this.logger.error('Failed to use referral code', error); + throw error; + } + } + async getReferralCodeByCode(code) { + const referralCode = await this.prisma.referralCode.findUnique({ + where: { code }, + include: { + usages: { + where: { isActive: true }, + }, + _count: { + select: { usages: true }, + }, + }, + }); + return referralCode; + } + async getReferralCodesByOwner(ownerAddress) { + const referralCodes = await this.prisma.referralCode.findMany({ + where: { ownerAddress }, + include: { + usages: { + where: { isActive: true }, + }, + _count: { + select: { usages: true }, + }, + }, + orderBy: { createdAt: 'desc' }, + }); + return referralCodes; + } + async getReferralUsageByAddress(refereeAddress) { + const usage = await this.prisma.referralUsage.findUnique({ + where: { refereeAddress }, + }); + return usage; + } + async deactivateReferralCode(code, ownerAddress) { + try { + const referralCode = await this.prisma.referralCode.findUnique({ + where: { code }, + }); + if (!referralCode) { + throw new common_1.NotFoundException('Referral code not found'); + } + if (referralCode.ownerAddress !== ownerAddress) { + throw new common_1.BadRequestException('Not authorized to deactivate this referral code'); + } + const updatedCode = await this.prisma.referralCode.update({ + where: { code }, + data: { isActive: false }, + }); + this.logger.log(`Deactivated referral code: ${code}`); + return updatedCode; + } + catch (error) { + this.logger.error('Failed to deactivate referral code', error); + throw error; + } + } + async getAllReferralCodes(limit = 50) { + const referralCodes = await this.prisma.referralCode.findMany({ + take: limit, + include: { + usages: { + where: { isActive: true }, + }, + _count: { + select: { usages: true }, + }, + }, + orderBy: { createdAt: 'desc' }, + }); + return referralCodes; + } + async getReferralStatsByOwner(ownerAddress, startDate, endDate) { + const dateFilter = startDate && endDate + ? { + usedAt: { + gte: startDate, + lte: endDate, + }, + } + : {}; + const referralCodes = await this.prisma.referralCode.findMany({ + where: { ownerAddress }, + include: { + usages: { + where: { + isActive: true, + ...dateFilter, + }, + }, + _count: { + select: { usages: true }, + }, + }, + }); + const totalReferrals = referralCodes.reduce((sum, code) => sum + code.usages.length, 0); + const activeCodesCount = referralCodes.filter((code) => code.isActive).length; + // Fetch fee data from swap service for all codes + let totalFeesCollectedUsd = 0; + let totalReferrerCommissionUsd = 0; + const referralCodesWithFees = await Promise.all(referralCodes.map(async (code) => { + try { + const feeData = await this.swapServiceClient.calculateReferralFees(code.code, startDate, endDate); + const feesCollected = parseFloat(feeData.totalFeesCollectedUsd || '0'); + const referrerCommission = parseFloat(feeData.referrerCommissionUsd || '0'); + totalFeesCollectedUsd += feesCollected; + totalReferrerCommissionUsd += referrerCommission; + return { + code: code.code, + isActive: code.isActive, + createdAt: code.createdAt, + usageCount: code.usages.length, + maxUses: code.maxUses, + expiresAt: code.expiresAt, + swapCount: feeData.swapCount || 0, + swapVolumeUsd: feeData.totalSwapVolumeUsd || '0', + feesCollectedUsd: feeData.totalFeesCollectedUsd || '0', + referrerCommissionUsd: feeData.referrerCommissionUsd || '0', + }; + } + catch (error) { + this.logger.warn(`Failed to fetch fees for code ${code.code}:`, error); + return { + code: code.code, + isActive: code.isActive, + createdAt: code.createdAt, + usageCount: code.usages.length, + maxUses: code.maxUses, + expiresAt: code.expiresAt, + swapCount: 0, + swapVolumeUsd: '0', + feesCollectedUsd: '0', + referrerCommissionUsd: '0', + }; + } + })); + return { + totalReferrals, + activeCodesCount, + totalCodesCount: referralCodes.length, + totalFeesCollectedUsd: totalFeesCollectedUsd.toFixed(2), + totalReferrerCommissionUsd: totalReferrerCommissionUsd.toFixed(2), + referralCodes: referralCodesWithFees, + }; + } +}; +exports.ReferralService = ReferralService; +exports.ReferralService = ReferralService = ReferralService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], ReferralService); diff --git a/apps/user-service/src/referral/referral.service.ts b/apps/user-service/src/referral/referral.service.ts index c389dc8..0b8c365 100644 --- a/apps/user-service/src/referral/referral.service.ts +++ b/apps/user-service/src/referral/referral.service.ts @@ -1,4 +1,9 @@ -import { Injectable, Logger, BadRequestException, NotFoundException } from '@nestjs/common'; +import { + Injectable, + Logger, + BadRequestException, + NotFoundException, +} from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { ReferralCode, ReferralUsage } from '@prisma/client'; import { SwapServiceClient } from '@shapeshift/shared-utils'; @@ -50,7 +55,9 @@ export class ReferralService { }, }); - this.logger.log(`Created referral code: ${data.code} for address ${data.ownerAddress}`); + this.logger.log( + `Created referral code: ${data.code} for address ${data.ownerAddress}`, + ); return referralCode; } catch (error) { this.logger.error('Failed to create referral code', error); @@ -81,7 +88,10 @@ export class ReferralService { throw new BadRequestException('Referral code has expired'); } - if (referralCode.maxUses && referralCode._count.usages >= referralCode.maxUses) { + if ( + referralCode.maxUses && + referralCode._count.usages >= referralCode.maxUses + ) { throw new BadRequestException('Referral code has reached maximum uses'); } @@ -94,7 +104,9 @@ export class ReferralService { }); if (existingUsage) { - throw new BadRequestException('Address has already used a referral code'); + throw new BadRequestException( + 'Address has already used a referral code', + ); } const usage = await this.prisma.referralUsage.create({ @@ -104,7 +116,9 @@ export class ReferralService { }, }); - this.logger.log(`Referral code ${data.code} used by ${data.refereeAddress}`); + this.logger.log( + `Referral code ${data.code} used by ${data.refereeAddress}`, + ); return usage; } catch (error) { this.logger.error('Failed to use referral code', error); @@ -112,7 +126,9 @@ export class ReferralService { } } - async getReferralCodeByCode(code: string): Promise { + async getReferralCodeByCode( + code: string, + ): Promise { const referralCode = await this.prisma.referralCode.findUnique({ where: { code }, include: { @@ -128,7 +144,9 @@ export class ReferralService { return referralCode; } - async getReferralCodesByOwner(ownerAddress: string): Promise { + async getReferralCodesByOwner( + ownerAddress: string, + ): Promise { const referralCodes = await this.prisma.referralCode.findMany({ where: { ownerAddress }, include: { @@ -145,7 +163,9 @@ export class ReferralService { return referralCodes; } - async getReferralUsageByAddress(refereeAddress: string): Promise { + async getReferralUsageByAddress( + refereeAddress: string, + ): Promise { const usage = await this.prisma.referralUsage.findUnique({ where: { refereeAddress }, }); @@ -153,7 +173,10 @@ export class ReferralService { return usage; } - async deactivateReferralCode(code: string, ownerAddress: string): Promise { + async deactivateReferralCode( + code: string, + ownerAddress: string, + ): Promise { try { const referralCode = await this.prisma.referralCode.findUnique({ where: { code }, @@ -164,7 +187,9 @@ export class ReferralService { } if (referralCode.ownerAddress !== ownerAddress) { - throw new BadRequestException('Not authorized to deactivate this referral code'); + throw new BadRequestException( + 'Not authorized to deactivate this referral code', + ); } const updatedCode = await this.prisma.referralCode.update({ @@ -197,13 +222,20 @@ export class ReferralService { return referralCodes; } - async getReferralStatsByOwner(ownerAddress: string, startDate?: Date, endDate?: Date) { - const dateFilter = startDate && endDate ? { - usedAt: { - gte: startDate, - lte: endDate, - }, - } : {}; + async getReferralStatsByOwner( + ownerAddress: string, + startDate?: Date, + endDate?: Date, + ) { + const dateFilter = + startDate && endDate + ? { + usedAt: { + gte: startDate, + lte: endDate, + }, + } + : {}; const referralCodes = await this.prisma.referralCode.findMany({ where: { ownerAddress }, @@ -225,7 +257,9 @@ export class ReferralService { 0, ); - const activeCodesCount = referralCodes.filter(code => code.isActive).length; + const activeCodesCount = referralCodes.filter( + (code) => code.isActive, + ).length; // Fetch fee data from swap service for all codes let totalFeesCollectedUsd = 0; @@ -240,8 +274,12 @@ export class ReferralService { endDate, ); - const feesCollected = parseFloat(feeData.totalFeesCollectedUsd || '0'); - const referrerCommission = parseFloat(feeData.referrerCommissionUsd || '0'); + const feesCollected = parseFloat( + feeData.totalFeesCollectedUsd || '0', + ); + const referrerCommission = parseFloat( + feeData.referrerCommissionUsd || '0', + ); totalFeesCollectedUsd += feesCollected; totalReferrerCommissionUsd += referrerCommission; @@ -259,7 +297,10 @@ export class ReferralService { referrerCommissionUsd: feeData.referrerCommissionUsd || '0', }; } catch (error) { - this.logger.warn(`Failed to fetch fees for code ${code.code}:`, error); + this.logger.warn( + `Failed to fetch fees for code ${code.code}:`, + error, + ); return { code: code.code, isActive: code.isActive, diff --git a/apps/user-service/src/users/users.controller.js b/apps/user-service/src/users/users.controller.js new file mode 100644 index 0000000..5489433 --- /dev/null +++ b/apps/user-service/src/users/users.controller.js @@ -0,0 +1,159 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.UsersController = void 0; +const common_1 = require("@nestjs/common"); +const users_service_1 = require("./users.service"); +let UsersController = class UsersController { + constructor(usersService) { + this.usersService = usersService; + } + async createUser(data) { + return this.usersService.createUser(data); + } + async getOrCreateUser(data) { + return this.usersService.getOrCreateUserByAccountIds(data.accountIds, data.referralCode); + } + async getAllUsers(limit) { + return this.usersService.getAllUsers(limit ? parseInt(limit) : 50); + } + async getUserById(userId) { + return this.usersService.getUserById(userId); + } + async getUserByAccountId(accountId) { + return this.usersService.getUserByAccountId(accountId); + } + async userExistsWithAccountId(accountId) { + const exists = await this.usersService.userExistsWithAccountId(accountId); + return { exists }; + } + async getOrCreateUserByAccountId(data) { + return this.usersService.getOrCreateUserByAccountId(data.accountId); + } + async addAccountIds(userId, data) { + return this.usersService.addAccountIds(userId, data.accountIds); + } + async addAccountId(userId, data) { + return this.usersService.addAccountId({ + userId, + accountId: data.accountId, + }); + } + async registerDevice(userId, data) { + return this.usersService.registerDevice({ + userId, + deviceToken: data.deviceToken, + deviceType: data.deviceType, + }); + } + async getUserDevices(userId) { + return this.usersService.getUserDevices(userId); + } + async removeDevice(userId, deviceId) { + return this.usersService.removeDevice(userId, deviceId); + } +}; +exports.UsersController = UsersController; +__decorate([ + (0, common_1.Post)(), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], UsersController.prototype, "createUser", null); +__decorate([ + (0, common_1.Post)('get-or-create'), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], UsersController.prototype, "getOrCreateUser", null); +__decorate([ + (0, common_1.Get)(), + __param(0, (0, common_1.Query)('limit')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", Promise) +], UsersController.prototype, "getAllUsers", null); +__decorate([ + (0, common_1.Get)(':userId'), + __param(0, (0, common_1.Param)('userId')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", Promise) +], UsersController.prototype, "getUserById", null); +__decorate([ + (0, common_1.Get)('account/:accountId'), + __param(0, (0, common_1.Param)('accountId')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", Promise) +], UsersController.prototype, "getUserByAccountId", null); +__decorate([ + (0, common_1.Get)('exists/account/:accountId'), + __param(0, (0, common_1.Param)('accountId')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", Promise) +], UsersController.prototype, "userExistsWithAccountId", null); +__decorate([ + (0, common_1.Post)('get-or-create-by-account'), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], UsersController.prototype, "getOrCreateUserByAccountId", null); +__decorate([ + (0, common_1.Post)(':userId/account-ids'), + __param(0, (0, common_1.Param)('userId')), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String, Object]), + __metadata("design:returntype", Promise) +], UsersController.prototype, "addAccountIds", null); +__decorate([ + (0, common_1.Post)(':userId/account-id'), + __param(0, (0, common_1.Param)('userId')), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String, Object]), + __metadata("design:returntype", Promise) +], UsersController.prototype, "addAccountId", null); +__decorate([ + (0, common_1.Post)(':userId/devices'), + __param(0, (0, common_1.Param)('userId')), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String, Object]), + __metadata("design:returntype", Promise) +], UsersController.prototype, "registerDevice", null); +__decorate([ + (0, common_1.Get)(':userId/devices'), + __param(0, (0, common_1.Param)('userId')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", Promise) +], UsersController.prototype, "getUserDevices", null); +__decorate([ + (0, common_1.Delete)(':userId/devices/:deviceId'), + __param(0, (0, common_1.Param)('userId')), + __param(1, (0, common_1.Param)('deviceId')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String, String]), + __metadata("design:returntype", Promise) +], UsersController.prototype, "removeDevice", null); +exports.UsersController = UsersController = __decorate([ + (0, common_1.Controller)('users'), + __metadata("design:paramtypes", [users_service_1.UsersService]) +], UsersController); diff --git a/apps/user-service/src/users/users.controller.ts b/apps/user-service/src/users/users.controller.ts index 1264c78..0557c27 100644 --- a/apps/user-service/src/users/users.controller.ts +++ b/apps/user-service/src/users/users.controller.ts @@ -1,4 +1,12 @@ -import { Controller, Post, Get, Put, Delete, Param, Body, Query } from '@nestjs/common'; +import { + Controller, + Post, + Get, + Delete, + Param, + Body, + Query, +} from '@nestjs/common'; import { UsersService } from './users.service'; import { CreateUserDto, DeviceType } from '@shapeshift/shared-types'; @@ -12,8 +20,13 @@ export class UsersController { } @Post('get-or-create') - async getOrCreateUser(@Body() data: { accountIds: string[]; referralCode?: string }) { - return this.usersService.getOrCreateUserByAccountIds(data.accountIds, data.referralCode); + async getOrCreateUser( + @Body() data: { accountIds: string[]; referralCode?: string }, + ) { + return this.usersService.getOrCreateUserByAccountIds( + data.accountIds, + data.referralCode, + ); } @Get() diff --git a/apps/user-service/src/users/users.service.js b/apps/user-service/src/users/users.service.js new file mode 100644 index 0000000..d643ff0 --- /dev/null +++ b/apps/user-service/src/users/users.service.js @@ -0,0 +1,347 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var UsersService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.UsersService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../prisma/prisma.service"); +const referral_service_1 = require("../referral/referral.service"); +const shared_utils_1 = require("@shapeshift/shared-utils"); +let UsersService = UsersService_1 = class UsersService { + constructor(prisma, referralService) { + this.prisma = prisma; + this.referralService = referralService; + this.logger = new common_1.Logger(UsersService_1.name); + } + // Type assertion helpers for Prisma results + assertDeviceType(deviceType) { + if (deviceType === 'MOBILE' || deviceType === 'WEB') { + return deviceType; + } + throw new Error(`Invalid device type: ${deviceType}`); + } + async findUserByHashedAccountId(hashedAccountId) { + const user = await this.prisma.user.findFirst({ + where: { + userAccounts: { + some: { + accountId: hashedAccountId, + }, + }, + }, + include: { + userAccounts: true, + devices: true, + }, + }); + return user; + } + async createUser(data) { + try { + const hashedAccountIds = data.accountIds.map((id) => (0, shared_utils_1.hashAccountId)(id)); + for (const hashedAccountId of hashedAccountIds) { + const existingUser = await this.findUserByHashedAccountId(hashedAccountId); + if (existingUser) { + this.logger.log(`User already exists with account ID: ${existingUser.id}`); + const newAccountIds = hashedAccountIds.filter((id) => !existingUser.userAccounts.some((ua) => ua.accountId === id)); + if (newAccountIds.length > 0) { + await this.addHashedAccountIds(existingUser.id, newAccountIds); + } + return this.getUserById(existingUser.id); + } + } + const user = await this.prisma.user.create({ + data: { + userAccounts: { + create: hashedAccountIds.map((id) => ({ + accountId: id, + })), + }, + }, + }); + this.logger.log(`Created new user: ${user.id} with ${hashedAccountIds.length} account IDs`); + return this.getUserById(user.id); + } + catch (error) { + this.logger.error('Failed to create user', error); + throw error; + } + } + async addAccountIds(userId, accountIds) { + try { + const hashedAccountIds = accountIds.map((id) => { + if (!(0, shared_utils_1.isValidAccountId)(id)) { + throw new Error('Invalid account ID'); + } + return (0, shared_utils_1.hashAccountId)(id); + }); + return this.addHashedAccountIds(userId, hashedAccountIds); + } + catch (error) { + this.logger.error('Failed to add account IDs', error); + throw error; + } + } + async addHashedAccountIds(userId, hashedAccountIds) { + try { + const userAccounts = await Promise.all(hashedAccountIds.map((hashedAccountId) => this.prisma.userAccount.upsert({ + where: { + userId_accountId: { + userId, + accountId: hashedAccountId, + }, + }, + update: {}, + create: { + userId, + accountId: hashedAccountId, + }, + }))); + this.logger.log(`Added ${userAccounts.length} account IDs for user ${userId}`); + return userAccounts; + } + catch (error) { + this.logger.error('Failed to add hashed account IDs', error); + throw error; + } + } + async addAccountId(data) { + try { + if (!(0, shared_utils_1.isValidAccountId)(data.accountId)) { + throw new Error('Invalid account ID'); + } + const hashedAccountId = (0, shared_utils_1.hashAccountId)(data.accountId); + const existingUser = await this.findUserByHashedAccountId(hashedAccountId); + if (existingUser && existingUser.id !== data.userId) { + throw new Error(`Account ID already belongs to user ${existingUser.id}`); + } + const userAccount = await this.prisma.userAccount.upsert({ + where: { + userId_accountId: { + userId: data.userId, + accountId: hashedAccountId, + }, + }, + update: {}, + create: { + userId: data.userId, + accountId: hashedAccountId, + }, + }); + this.logger.log(`Added account ID for user ${data.userId}`); + return userAccount; + } + catch (error) { + this.logger.error('Failed to add account ID', error); + throw error; + } + } + async getUserById(userId) { + const user = await this.prisma.user.findUnique({ + where: { id: userId }, + include: { + userAccounts: true, + devices: true, + }, + }); + return user; + } + async getUserByAccountId(accountId) { + if (!(0, shared_utils_1.isValidAccountId)(accountId)) { + throw new Error('Invalid account ID'); + } + const hashedAccountId = (0, shared_utils_1.hashAccountId)(accountId); + const user = await this.prisma.user.findFirst({ + where: { + userAccounts: { + some: { + accountId: hashedAccountId, + }, + }, + }, + include: { + userAccounts: true, + devices: true, + }, + }); + return user; + } + async getAllUsers(limit = 50) { + const users = await this.prisma.user.findMany({ + take: limit, + include: { + userAccounts: true, + devices: { + where: { isActive: true }, + }, + }, + orderBy: { createdAt: 'desc' }, + }); + return users; + } + async userExistsWithAccountId(accountId) { + if (!(0, shared_utils_1.isValidAccountId)(accountId)) { + return false; + } + const hashedAccountId = (0, shared_utils_1.hashAccountId)(accountId); + const user = await this.findUserByHashedAccountId(hashedAccountId); + return !!user; + } + async getOrCreateUserByAccountId(accountId) { + if (!(0, shared_utils_1.isValidAccountId)(accountId)) { + throw new Error('Invalid account ID'); + } + const hashedAccountId = (0, shared_utils_1.hashAccountId)(accountId); + const existingUser = await this.findUserByHashedAccountId(hashedAccountId); + if (existingUser) { + this.logger.log(`Found existing user: ${existingUser.id} for account ID`); + return existingUser; + } + return this.createUser({ + accountIds: [accountId], + }); + } + async getOrCreateUserByAccountIds(accountIds, referralCode) { + this.logger.log(`getOrCreateUserByAccountIds called with accountIds: ${JSON.stringify(accountIds)}, referralCode: ${referralCode}`); + if (!accountIds || accountIds.length === 0) { + throw new Error('At least one account ID is required'); + } + // Validate all account IDs + accountIds.forEach((id) => { + if (!(0, shared_utils_1.isValidAccountId)(id)) { + throw new Error(`Invalid account ID: ${id}`); + } + }); + const hashedAccountIds = accountIds.map((id) => (0, shared_utils_1.hashAccountId)(id)); + this.logger.log(`Hashed account IDs: ${JSON.stringify(hashedAccountIds)}`); + for (const hashedAccountId of hashedAccountIds) { + const existingUser = await this.findUserByHashedAccountId(hashedAccountId); + if (existingUser) { + this.logger.log(`Found existing user: ${existingUser.id} for account ID`); + const newAccountIds = hashedAccountIds.filter((id) => !existingUser.userAccounts.some((ua) => ua.accountId === id)); + if (newAccountIds.length > 0) { + await this.addHashedAccountIds(existingUser.id, newAccountIds); + } + // Try to apply referral code for existing user if provided + if (referralCode) { + try { + // Use the first hashed account ID as referee address for privacy + await this.referralService.useReferralCode({ + code: referralCode, + refereeAddress: hashedAccountIds[0], + }); + this.logger.log(`Successfully applied referral code ${referralCode} for existing user ${existingUser.id}`); + } + catch (error) { + // Log but don't fail - user might already be referred or code might be invalid + this.logger.log(`Could not apply referral code ${referralCode} for existing user ${existingUser.id}: ${error instanceof Error ? error.message : String(error)}`); + } + } + const result = await this.getUserById(existingUser.id); + this.logger.log(`Returning existing user: ${result?.id}`); + return result; + } + } + this.logger.log(`No existing user found, creating new user with ${hashedAccountIds.length} account IDs`); + const user = await this.prisma.user.create({ + data: { + userAccounts: { + create: hashedAccountIds.map((id) => ({ + accountId: id, + })), + }, + }, + }); + this.logger.log(`Created new user: ${user.id} with ${hashedAccountIds.length} account IDs`); + // Handle referral code if provided + if (referralCode) { + try { + // Use the first hashed account ID as referee address for privacy + await this.referralService.useReferralCode({ + code: referralCode, + refereeAddress: hashedAccountIds[0], + }); + this.logger.log(`Successfully applied referral code ${referralCode} for new user ${user.id}`); + } + catch (error) { + this.logger.warn(`Failed to apply referral code ${referralCode} for user ${user.id}:`, error); + // Don't fail user creation if referral code application fails + } + } + const result = await this.getUserById(user.id); + this.logger.log(`Returning new user: ${result?.id}`); + return result; + } + async registerDevice(data) { + try { + // Check if user exists + const user = await this.getUserById(data.userId); + if (!user) { + throw new Error('User not found'); + } + const device = await this.prisma.device.upsert({ + where: { + deviceToken: data.deviceToken, + }, + update: { + userId: data.userId, + deviceType: data.deviceType, + isActive: true, + }, + create: { + deviceToken: data.deviceToken, + deviceType: data.deviceType, + userId: data.userId, + }, + }); + this.logger.log(`Device registered: ${data.deviceToken} for user ${data.userId} (${data.deviceType})`); + return device; + } + catch (error) { + this.logger.error('Failed to register device', error); + throw error; + } + } + async getUserDevices(userId) { + const devices = await this.prisma.device.findMany({ + where: { + userId, + isActive: true, + }, + }); + return devices; + } + async removeDevice(userId, deviceId) { + try { + await this.prisma.device.updateMany({ + where: { + id: deviceId, + userId, + }, + data: { + isActive: false, + }, + }); + this.logger.log(`Removed device ${deviceId} for user ${userId}`); + return { success: true }; + } + catch (error) { + this.logger.error('Failed to remove device', error); + throw error; + } + } +}; +exports.UsersService = UsersService; +exports.UsersService = UsersService = UsersService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService, + referral_service_1.ReferralService]) +], UsersService); diff --git a/apps/user-service/src/users/users.service.ts b/apps/user-service/src/users/users.service.ts index feab316..d587cf4 100644 --- a/apps/user-service/src/users/users.service.ts +++ b/apps/user-service/src/users/users.service.ts @@ -6,11 +6,10 @@ import { CreateUserDto, AddAccountIdDto, RegisterDeviceDto, - DeviceType + DeviceType, } from '@shapeshift/shared-types'; import { User, UserAccount, Device } from '@prisma/client'; - @Injectable() export class UsersService { private readonly logger = new Logger(UsersService.name); @@ -47,21 +46,25 @@ export class UsersService { async createUser(data: CreateUserDto): Promise { try { - const hashedAccountIds = data.accountIds.map(id => hashAccountId(id)); - + const hashedAccountIds = data.accountIds.map((id) => hashAccountId(id)); + for (const hashedAccountId of hashedAccountIds) { - const existingUser = await this.findUserByHashedAccountId(hashedAccountId); + const existingUser = + await this.findUserByHashedAccountId(hashedAccountId); if (existingUser) { - this.logger.log(`User already exists with account ID: ${existingUser.id}`); - - const newAccountIds = hashedAccountIds.filter(id => - !existingUser.userAccounts.some(ua => ua.accountId === id) + this.logger.log( + `User already exists with account ID: ${existingUser.id}`, + ); + + const newAccountIds = hashedAccountIds.filter( + (id) => + !existingUser.userAccounts.some((ua) => ua.accountId === id), ); - + if (newAccountIds.length > 0) { await this.addHashedAccountIds(existingUser.id, newAccountIds); } - + return this.getUserById(existingUser.id); } } @@ -69,14 +72,16 @@ export class UsersService { const user = await this.prisma.user.create({ data: { userAccounts: { - create: hashedAccountIds.map(id => ({ + create: hashedAccountIds.map((id) => ({ accountId: id, })), }, }, }); - this.logger.log(`Created new user: ${user.id} with ${hashedAccountIds.length} account IDs`); + this.logger.log( + `Created new user: ${user.id} with ${hashedAccountIds.length} account IDs`, + ); return this.getUserById(user.id); } catch (error) { this.logger.error('Failed to create user', error); @@ -84,9 +89,12 @@ export class UsersService { } } - async addAccountIds(userId: string, accountIds: string[]): Promise { + async addAccountIds( + userId: string, + accountIds: string[], + ): Promise { try { - const hashedAccountIds = accountIds.map(id => { + const hashedAccountIds = accountIds.map((id) => { if (!isValidAccountId(id)) { throw new Error('Invalid account ID'); } @@ -100,10 +108,13 @@ export class UsersService { } } - private async addHashedAccountIds(userId: string, hashedAccountIds: string[]): Promise { + private async addHashedAccountIds( + userId: string, + hashedAccountIds: string[], + ): Promise { try { const userAccounts = await Promise.all( - hashedAccountIds.map(hashedAccountId => + hashedAccountIds.map((hashedAccountId) => this.prisma.userAccount.upsert({ where: { userId_accountId: { @@ -116,11 +127,13 @@ export class UsersService { userId, accountId: hashedAccountId, }, - }) - ) + }), + ), ); - this.logger.log(`Added ${userAccounts.length} account IDs for user ${userId}`); + this.logger.log( + `Added ${userAccounts.length} account IDs for user ${userId}`, + ); return userAccounts; } catch (error) { this.logger.error('Failed to add hashed account IDs', error); @@ -135,10 +148,13 @@ export class UsersService { } const hashedAccountId = hashAccountId(data.accountId); - - const existingUser = await this.findUserByHashedAccountId(hashedAccountId); + + const existingUser = + await this.findUserByHashedAccountId(hashedAccountId); if (existingUser && existingUser.id !== data.userId) { - throw new Error(`Account ID already belongs to user ${existingUser.id}`); + throw new Error( + `Account ID already belongs to user ${existingUser.id}`, + ); } const userAccount = await this.prisma.userAccount.upsert({ @@ -180,7 +196,7 @@ export class UsersService { } const hashedAccountId = hashAccountId(accountId); - + const user = await this.prisma.user.findFirst({ where: { userAccounts: { @@ -228,7 +244,7 @@ export class UsersService { const hashedAccountId = hashAccountId(accountId); const existingUser = await this.findUserByHashedAccountId(hashedAccountId); - + if (existingUser) { this.logger.log(`Found existing user: ${existingUser.id} for account ID`); return existingUser; @@ -239,30 +255,38 @@ export class UsersService { }); } - async getOrCreateUserByAccountIds(accountIds: string[], referralCode?: string): Promise { - this.logger.log(`getOrCreateUserByAccountIds called with accountIds: ${JSON.stringify(accountIds)}, referralCode: ${referralCode}`); + async getOrCreateUserByAccountIds( + accountIds: string[], + referralCode?: string, + ): Promise { + this.logger.log( + `getOrCreateUserByAccountIds called with accountIds: ${JSON.stringify(accountIds)}, referralCode: ${referralCode}`, + ); if (!accountIds || accountIds.length === 0) { throw new Error('At least one account ID is required'); } // Validate all account IDs - accountIds.forEach(id => { + accountIds.forEach((id) => { if (!isValidAccountId(id)) { throw new Error(`Invalid account ID: ${id}`); } }); - const hashedAccountIds = accountIds.map(id => hashAccountId(id)); + const hashedAccountIds = accountIds.map((id) => hashAccountId(id)); this.logger.log(`Hashed account IDs: ${JSON.stringify(hashedAccountIds)}`); for (const hashedAccountId of hashedAccountIds) { - const existingUser = await this.findUserByHashedAccountId(hashedAccountId); + const existingUser = + await this.findUserByHashedAccountId(hashedAccountId); if (existingUser) { - this.logger.log(`Found existing user: ${existingUser.id} for account ID`); + this.logger.log( + `Found existing user: ${existingUser.id} for account ID`, + ); - const newAccountIds = hashedAccountIds.filter(id => - !existingUser.userAccounts.some(ua => ua.accountId === id) + const newAccountIds = hashedAccountIds.filter( + (id) => !existingUser.userAccounts.some((ua) => ua.accountId === id), ); if (newAccountIds.length > 0) { @@ -277,31 +301,39 @@ export class UsersService { code: referralCode, refereeAddress: hashedAccountIds[0], }); - this.logger.log(`Successfully applied referral code ${referralCode} for existing user ${existingUser.id}`); + this.logger.log( + `Successfully applied referral code ${referralCode} for existing user ${existingUser.id}`, + ); } catch (error) { // Log but don't fail - user might already be referred or code might be invalid - this.logger.log(`Could not apply referral code ${referralCode} for existing user ${existingUser.id}: ${error.message}`); + this.logger.log( + `Could not apply referral code ${referralCode} for existing user ${existingUser.id}: ${error instanceof Error ? error.message : String(error)}`, + ); } } const result = await this.getUserById(existingUser.id); this.logger.log(`Returning existing user: ${result?.id}`); - return result!; + return result; } } - this.logger.log(`No existing user found, creating new user with ${hashedAccountIds.length} account IDs`); + this.logger.log( + `No existing user found, creating new user with ${hashedAccountIds.length} account IDs`, + ); const user = await this.prisma.user.create({ data: { userAccounts: { - create: hashedAccountIds.map(id => ({ + create: hashedAccountIds.map((id) => ({ accountId: id, })), }, }, }); - this.logger.log(`Created new user: ${user.id} with ${hashedAccountIds.length} account IDs`); + this.logger.log( + `Created new user: ${user.id} with ${hashedAccountIds.length} account IDs`, + ); // Handle referral code if provided if (referralCode) { @@ -311,16 +343,21 @@ export class UsersService { code: referralCode, refereeAddress: hashedAccountIds[0], }); - this.logger.log(`Successfully applied referral code ${referralCode} for new user ${user.id}`); + this.logger.log( + `Successfully applied referral code ${referralCode} for new user ${user.id}`, + ); } catch (error) { - this.logger.warn(`Failed to apply referral code ${referralCode} for user ${user.id}:`, error); + this.logger.warn( + `Failed to apply referral code ${referralCode} for user ${user.id}:`, + error, + ); // Don't fail user creation if referral code application fails } } const result = await this.getUserById(user.id); this.logger.log(`Returning new user: ${result?.id}`); - return result!; + return result; } async registerDevice(data: RegisterDeviceDto): Promise { @@ -347,7 +384,9 @@ export class UsersService { }, }); - this.logger.log(`Device registered: ${data.deviceToken} for user ${data.userId} (${data.deviceType})`); + this.logger.log( + `Device registered: ${data.deviceToken} for user ${data.userId} (${data.deviceType})`, + ); return device; } catch (error) { this.logger.error('Failed to register device', error); @@ -365,7 +404,10 @@ export class UsersService { return devices; } - async removeDevice(userId: string, deviceId: string): Promise<{ success: boolean }> { + async removeDevice( + userId: string, + deviceId: string, + ): Promise<{ success: boolean }> { try { await this.prisma.device.updateMany({ where: { diff --git a/packages/shared-types/src/index.js b/packages/shared-types/src/index.js new file mode 100644 index 0000000..c8ad2e5 --- /dev/null +++ b/packages/shared-types/src/index.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/packages/shared-types/src/index.ts b/packages/shared-types/src/index.ts index 9b6d972..c2cf81d 100644 --- a/packages/shared-types/src/index.ts +++ b/packages/shared-types/src/index.ts @@ -65,10 +65,10 @@ export interface PushNotificationData { export interface CreateSwapDto { swapId: string; - userId: string; + userId?: string; sellAsset: Asset; buyAsset: Asset; - sellTxHash: string; + sellTxHash?: string; sellAmountCryptoBaseUnit: string; expectedBuyAmountCryptoBaseUnit: string; sellAmountCryptoPrecision: string; @@ -81,6 +81,8 @@ export interface CreateSwapDto { isStreaming?: boolean; metadata?: Record; affiliateAddress?: string; + affiliateBps?: string; + origin?: 'web' | 'api' | 'widget'; } export interface UpdateSwapStatusDto { @@ -127,6 +129,7 @@ export interface SwapVerificationResult { hasAffiliate: boolean; affiliateBps?: number; affiliateAddress?: string; + verifiedSellAmountCryptoBaseUnit?: string; protocol: string; swapId: string; details?: Record; diff --git a/packages/shared-utils/src/index.js b/packages/shared-utils/src/index.js new file mode 100644 index 0000000..aa47be4 --- /dev/null +++ b/packages/shared-utils/src/index.js @@ -0,0 +1,115 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SwapServiceClient = exports.NotificationsServiceClient = exports.UserServiceClient = exports.getOptionalEnvVar = exports.getRequiredEnvVar = exports.createPaginatedResponse = exports.createError = exports.parseDate = exports.formatDate = exports.isValidAccountId = exports.hashAccountId = void 0; +const crypto = __importStar(require("crypto")); +const caip_1 = require("@shapeshiftoss/caip"); +// Hash utilities +const hashAccountId = (accountId, salt) => { + const defaultSalt = process.env.ACCOUNT_ID_SALT || 'default-salt-change-in-production'; + const saltToUse = salt || defaultSalt; + return crypto + .createHash('sha256') + .update(accountId + saltToUse) + .digest('hex'); +}; +exports.hashAccountId = hashAccountId; +// Validation utilities +const isValidAccountId = (accountId) => { + try { + // Try to parse using the library - this works for supported chains + (0, caip_1.fromAccountId)(accountId); + return true; + } + catch { + // If library parsing fails, check if it at least matches CAIP-10 format + // Format: chainNamespace:chainReference:accountAddress + // Examples: + // - EVM: eip155:1:0x1234567890abcdef1234567890abcdef12345678 + // - Tron: tron:0x2b6653dc:TUrqPcjwrdz4u2tgSsrUoPHYYLe32u4Zea + // - Bitcoin: bip122:000000000019d6689c085ae165831e93:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa + // - Solana: solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK + // - Cosmos: cosmos:cosmoshub-4:cosmos1..., cosmos:mayachain-mainnet:maya1... + // More permissive CAIP-10 regex that handles various address formats + const caip10Regex = /^[a-z0-9-]+:[a-z0-9-]+:[a-zA-Z0-9]+$/; + return caip10Regex.test(accountId); + } +}; +exports.isValidAccountId = isValidAccountId; +// Date utilities +const formatDate = (date) => { + return date.toISOString(); +}; +exports.formatDate = formatDate; +const parseDate = (dateString) => { + return new Date(dateString); +}; +exports.parseDate = parseDate; +const createError = (message, code, details) => { + const error = new Error(message); + error.code = code; + error.details = details; + return error; +}; +exports.createError = createError; +// Pagination utilities +const createPaginatedResponse = (data, total, page, limit) => { + return { + data, + total, + page, + limit, + hasMore: page * limit < total, + }; +}; +exports.createPaginatedResponse = createPaginatedResponse; +// Environment utilities +const getRequiredEnvVar = (name) => { + const value = process.env[name]; + if (!value) { + throw new Error(`Required environment variable ${name} is not set`); + } + return value; +}; +exports.getRequiredEnvVar = getRequiredEnvVar; +const getOptionalEnvVar = (name, defaultValue) => { + return process.env[name] || defaultValue; +}; +exports.getOptionalEnvVar = getOptionalEnvVar; +// Service clients +var service_clients_1 = require("./service-clients"); +Object.defineProperty(exports, "UserServiceClient", { enumerable: true, get: function () { return service_clients_1.UserServiceClient; } }); +Object.defineProperty(exports, "NotificationsServiceClient", { enumerable: true, get: function () { return service_clients_1.NotificationsServiceClient; } }); +Object.defineProperty(exports, "SwapServiceClient", { enumerable: true, get: function () { return service_clients_1.SwapServiceClient; } }); diff --git a/packages/shared-utils/src/index.ts b/packages/shared-utils/src/index.ts index 9c1ccd7..56070c9 100644 --- a/packages/shared-utils/src/index.ts +++ b/packages/shared-utils/src/index.ts @@ -3,9 +3,13 @@ import { fromAccountId } from '@shapeshiftoss/caip'; // Hash utilities export const hashAccountId = (accountId: string, salt?: string): string => { - const defaultSalt = process.env.ACCOUNT_ID_SALT || 'default-salt-change-in-production'; + const defaultSalt = + process.env.ACCOUNT_ID_SALT || 'default-salt-change-in-production'; const saltToUse = salt || defaultSalt; - return crypto.createHash('sha256').update(accountId + saltToUse).digest('hex'); + return crypto + .createHash('sha256') + .update(accountId + saltToUse) + .digest('hex'); }; // Validation utilities @@ -14,7 +18,7 @@ export const isValidAccountId = (accountId: string): boolean => { // Try to parse using the library - this works for supported chains fromAccountId(accountId); return true; - } catch (error) { + } catch { // If library parsing fails, check if it at least matches CAIP-10 format // Format: chainNamespace:chainReference:accountAddress // Examples: @@ -40,10 +44,19 @@ export const parseDate = (dateString: string): Date => { }; // Error utilities -export const createError = (message: string, code?: string, details?: any): Error => { - const error = new Error(message); - (error as any).code = code; - (error as any).details = details; +interface AppError extends Error { + code?: string; + details?: unknown; +} + +export const createError = ( + message: string, + code?: string, + details?: unknown, +): AppError => { + const error = new Error(message) as AppError; + error.code = code; + error.details = details; return error; }; @@ -52,14 +65,14 @@ export const createPaginatedResponse = ( data: T[], total: number, page: number, - limit: number + limit: number, ) => { return { data, total, page, limit, - hasMore: page * limit < total + hasMore: page * limit < total, }; }; @@ -72,9 +85,16 @@ export const getRequiredEnvVar = (name: string): string => { return value; }; -export const getOptionalEnvVar = (name: string, defaultValue?: string): string | undefined => { +export const getOptionalEnvVar = ( + name: string, + defaultValue?: string, +): string | undefined => { return process.env[name] || defaultValue; }; // Service clients -export { UserServiceClient, NotificationsServiceClient, SwapServiceClient } from './service-clients'; +export { + UserServiceClient, + NotificationsServiceClient, + SwapServiceClient, +} from './service-clients'; diff --git a/packages/shared-utils/src/service-clients.js b/packages/shared-utils/src/service-clients.js new file mode 100644 index 0000000..35bae2f --- /dev/null +++ b/packages/shared-utils/src/service-clients.js @@ -0,0 +1,106 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SwapServiceClient = exports.NotificationsServiceClient = exports.UserServiceClient = void 0; +const axios_1 = __importDefault(require("axios")); +const index_1 = require("./index"); +class UserServiceClient { + constructor() { + const baseUrl = (0, index_1.getRequiredEnvVar)('USER_SERVICE_URL'); + this.axios = axios_1.default.create({ + baseURL: baseUrl, + headers: { + 'Content-Type': 'application/json', + }, + }); + } + async getUserById(userId) { + const response = await this.axios.get(`/users/${userId}`); + return response.data; + } + async getUserByAccountId(accountId) { + const response = await this.axios.get(`/users/account/${accountId}`); + return response.data; + } + async getOrCreateUserByAccountIds(accountIds) { + const response = await this.axios.post('/users/get-or-create', { + accountIds, + }); + return response.data; + } + async getUserDevices(userId) { + const response = await this.axios.get(`/users/${userId}/devices`); + return response.data; + } + async getUserReferralCode(userId) { + try { + const user = await this.getUserById(userId); + if (!user || !user.userAccounts || user.userAccounts.length === 0) { + return null; + } + // Get the first account's hashed ID to check referral usage + const hashedAccountId = user.userAccounts[0].accountId; + const response = await this.axios.get(`/referrals/usage/${hashedAccountId}`); + return response.data?.referralCode || null; + } + catch { + // If no referral usage found, return null + return null; + } + } + async getReferralUsages(referralCode) { + try { + const response = await this.axios.get(`/referrals/codes/${referralCode}`); + return response.data?.usages || []; + } + catch { + // If code not found or no usages, return empty array + return []; + } + } +} +exports.UserServiceClient = UserServiceClient; +class NotificationsServiceClient { + constructor() { + const baseUrl = (0, index_1.getRequiredEnvVar)('NOTIFICATIONS_SERVICE_URL'); + this.axios = axios_1.default.create({ + baseURL: baseUrl, + headers: { + 'Content-Type': 'application/json', + }, + }); + } + async createNotification(data) { + const response = await this.axios.post('/notifications', data); + return response.data; + } + async sendNotificationToUser(data) { + const response = await this.axios.post('/notifications/send-to-user', data); + return response.data; + } +} +exports.NotificationsServiceClient = NotificationsServiceClient; +class SwapServiceClient { + constructor() { + const baseUrl = (0, index_1.getRequiredEnvVar)('SWAP_SERVICE_URL'); + this.axios = axios_1.default.create({ + baseURL: baseUrl, + headers: { + 'Content-Type': 'application/json', + }, + }); + } + async calculateReferralFees(referralCode, startDate, endDate) { + const params = new URLSearchParams(); + if (startDate) + params.append('startDate', startDate.toISOString()); + if (endDate) + params.append('endDate', endDate.toISOString()); + const url = `/swaps/referral-fees/${referralCode}${params.toString() ? `?${params.toString()}` : ''}`; + const response = await this.axios.get(url); + return response.data; + } +} +exports.SwapServiceClient = SwapServiceClient; diff --git a/packages/shared-utils/src/service-clients.ts b/packages/shared-utils/src/service-clients.ts index 3953646..701e2fe 100644 --- a/packages/shared-utils/src/service-clients.ts +++ b/packages/shared-utils/src/service-clients.ts @@ -26,7 +26,9 @@ export class UserServiceClient { } async getOrCreateUserByAccountIds(accountIds: string[]): Promise { - const response = await this.axios.post('/users/get-or-create', { accountIds }); + const response = await this.axios.post('/users/get-or-create', { + accountIds, + }); return response.data; } @@ -45,23 +47,25 @@ export class UserServiceClient { // Get the first account's hashed ID to check referral usage const hashedAccountId = user.userAccounts[0].accountId; const response = await this.axios.get<{ referralCode: string } | null>( - `/referrals/usage/${hashedAccountId}` + `/referrals/usage/${hashedAccountId}`, ); return response.data?.referralCode || null; - } catch (error) { + } catch { // If no referral usage found, return null return null; } } - async getReferralUsages(referralCode: string): Promise> { + async getReferralUsages( + referralCode: string, + ): Promise> { try { const response = await this.axios.get<{ code: string; usages: Array<{ refereeAddress: string; usedAt: string }>; }>(`/referrals/codes/${referralCode}`); return response.data?.usages || []; - } catch (error) { + } catch { // If code not found or no usages, return empty array return []; } @@ -81,8 +85,13 @@ export class NotificationsServiceClient { }); } - async createNotification(data: CreateNotificationDto) { - const response = await this.axios.post('/notifications', data); + async createNotification( + data: CreateNotificationDto, + ): Promise> { + const response = await this.axios.post>( + '/notifications', + data, + ); return response.data; } @@ -90,13 +99,23 @@ export class NotificationsServiceClient { userId: string; title: string; body: string; - data?: any; - }) { - const response = await this.axios.post('/notifications/send-to-user', data); + data?: Record; + }): Promise> { + const response = await this.axios.post>( + '/notifications/send-to-user', + data, + ); return response.data; } } +interface ReferralFeeData { + swapCount: number; + totalSwapVolumeUsd: string; + totalFeesCollectedUsd: string; + referrerCommissionUsd: string; +} + export class SwapServiceClient { private readonly axios: AxiosInstance; @@ -110,13 +129,17 @@ export class SwapServiceClient { }); } - async calculateReferralFees(referralCode: string, startDate?: Date, endDate?: Date) { + async calculateReferralFees( + referralCode: string, + startDate?: Date, + endDate?: Date, + ): Promise { const params = new URLSearchParams(); if (startDate) params.append('startDate', startDate.toISOString()); if (endDate) params.append('endDate', endDate.toISOString()); const url = `/swaps/referral-fees/${referralCode}${params.toString() ? `?${params.toString()}` : ''}`; - const response = await this.axios.get(url); + const response = await this.axios.get(url); return response.data; } } From a63c2eeb9369a8e2c5ef2fd69da01ddde070adfe Mon Sep 17 00:00:00 2001 From: NeOMakinG <14963751+NeOMakinG@users.noreply.github.com> Date: Tue, 24 Feb 2026 13:57:30 +0100 Subject: [PATCH 3/6] fix: lint --- apps/notifications-service/prisma.config.js | 6 ++++++ apps/user-service/prisma.config.js | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 apps/notifications-service/prisma.config.js create mode 100644 apps/user-service/prisma.config.js diff --git a/apps/notifications-service/prisma.config.js b/apps/notifications-service/prisma.config.js new file mode 100644 index 0000000..196f9cc --- /dev/null +++ b/apps/notifications-service/prisma.config.js @@ -0,0 +1,6 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +require("dotenv/config"); +exports.default = { + schema: 'prisma/schema.prisma', +}; diff --git a/apps/user-service/prisma.config.js b/apps/user-service/prisma.config.js new file mode 100644 index 0000000..196f9cc --- /dev/null +++ b/apps/user-service/prisma.config.js @@ -0,0 +1,6 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +require("dotenv/config"); +exports.default = { + schema: 'prisma/schema.prisma', +}; From 2a749814f09a8ce832b233e855e3163db52c75e9 Mon Sep 17 00:00:00 2001 From: NeOMakinG <14963751+NeOMakinG@users.noreply.github.com> Date: Wed, 25 Feb 2026 14:03:54 +0100 Subject: [PATCH 4/6] chore: remove compiled JS files from tracking and update .gitignore --- .gitignore | 4 + apps/notifications-service/src/app.module.js | 32 - apps/notifications-service/src/main.js | 22 - .../notifications/notifications.controller.js | 88 -- .../notifications/notifications.service.js | 201 ---- .../src/prisma/prisma.service.js | 23 - .../src/websocket/websocket.gateway.js | 80 -- apps/swap-service/src/app.module.js | 53 -- .../src/lib/chain-adapter-init.service.js | 55 -- .../src/lib/chain-adapter-manager.service.js | 25 - .../lib/chain-adapters/cosmos-sdk.service.js | 132 --- .../src/lib/chain-adapters/evm.service.js | 220 ----- .../src/lib/chain-adapters/solana.service.js | 94 -- .../src/lib/chain-adapters/utxo.service.js | 145 --- apps/swap-service/src/main.js | 26 - .../src/polling/swap-polling.service.js | 69 -- .../swap-service/src/prisma/prisma.service.js | 23 - .../src/swaps/swaps.controller.js | 162 ---- apps/swap-service/src/swaps/swaps.service.js | 708 --------------- .../src/utils/affiliateFeeAsset.js | 57 -- apps/swap-service/src/utils/pricing.js | 59 -- .../verification/swap-verification.service.js | 859 ------------------ .../src/websocket/websocket.gateway.js | 80 -- apps/user-service/src/app.module.js | 31 - apps/user-service/src/main.js | 22 - .../user-service/src/prisma/prisma.service.js | 23 - .../src/referral/referral.controller.js | 135 --- .../src/referral/referral.service.js | 244 ----- .../src/users/users.controller.js | 159 ---- apps/user-service/src/users/users.service.js | 347 ------- packages/shared-types/src/index.js | 2 - packages/shared-utils/src/index.js | 115 --- packages/shared-utils/src/service-clients.js | 106 --- 33 files changed, 4 insertions(+), 4397 deletions(-) delete mode 100644 apps/notifications-service/src/app.module.js delete mode 100644 apps/notifications-service/src/main.js delete mode 100644 apps/notifications-service/src/notifications/notifications.controller.js delete mode 100644 apps/notifications-service/src/notifications/notifications.service.js delete mode 100644 apps/notifications-service/src/prisma/prisma.service.js delete mode 100644 apps/notifications-service/src/websocket/websocket.gateway.js delete mode 100644 apps/swap-service/src/app.module.js delete mode 100644 apps/swap-service/src/lib/chain-adapter-init.service.js delete mode 100644 apps/swap-service/src/lib/chain-adapter-manager.service.js delete mode 100644 apps/swap-service/src/lib/chain-adapters/cosmos-sdk.service.js delete mode 100644 apps/swap-service/src/lib/chain-adapters/evm.service.js delete mode 100644 apps/swap-service/src/lib/chain-adapters/solana.service.js delete mode 100644 apps/swap-service/src/lib/chain-adapters/utxo.service.js delete mode 100644 apps/swap-service/src/main.js delete mode 100644 apps/swap-service/src/polling/swap-polling.service.js delete mode 100644 apps/swap-service/src/prisma/prisma.service.js delete mode 100644 apps/swap-service/src/swaps/swaps.controller.js delete mode 100644 apps/swap-service/src/swaps/swaps.service.js delete mode 100644 apps/swap-service/src/utils/affiliateFeeAsset.js delete mode 100644 apps/swap-service/src/utils/pricing.js delete mode 100644 apps/swap-service/src/verification/swap-verification.service.js delete mode 100644 apps/swap-service/src/websocket/websocket.gateway.js delete mode 100644 apps/user-service/src/app.module.js delete mode 100644 apps/user-service/src/main.js delete mode 100644 apps/user-service/src/prisma/prisma.service.js delete mode 100644 apps/user-service/src/referral/referral.controller.js delete mode 100644 apps/user-service/src/referral/referral.service.js delete mode 100644 apps/user-service/src/users/users.controller.js delete mode 100644 apps/user-service/src/users/users.service.js delete mode 100644 packages/shared-types/src/index.js delete mode 100644 packages/shared-utils/src/index.js delete mode 100644 packages/shared-utils/src/service-clients.js diff --git a/.gitignore b/.gitignore index c076ebc..04f4e1f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,10 @@ node_modules build .turbo +# TypeScript compiled JS alongside source (use dist/ instead) +apps/*/src/**/*.js +packages/*/src/**/*.js + .yarn/* !.yarn/patches !.yarn/plugins diff --git a/apps/notifications-service/src/app.module.js b/apps/notifications-service/src/app.module.js deleted file mode 100644 index 9e15a0c..0000000 --- a/apps/notifications-service/src/app.module.js +++ /dev/null @@ -1,32 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.AppModule = void 0; -const common_1 = require("@nestjs/common"); -const config_1 = require("@nestjs/config"); -const axios_1 = require("@nestjs/axios"); -const notifications_controller_1 = require("./notifications/notifications.controller"); -const notifications_service_1 = require("./notifications/notifications.service"); -const websocket_gateway_1 = require("./websocket/websocket.gateway"); -const prisma_service_1 = require("./prisma/prisma.service"); -let AppModule = class AppModule { -}; -exports.AppModule = AppModule; -exports.AppModule = AppModule = __decorate([ - (0, common_1.Module)({ - imports: [ - config_1.ConfigModule.forRoot({ - isGlobal: true, - envFilePath: '../.env', - }), - axios_1.HttpModule, - ], - controllers: [notifications_controller_1.NotificationsController], - providers: [notifications_service_1.NotificationsService, websocket_gateway_1.WebsocketGateway, prisma_service_1.PrismaService], - }) -], AppModule); diff --git a/apps/notifications-service/src/main.js b/apps/notifications-service/src/main.js deleted file mode 100644 index b3ce02f..0000000 --- a/apps/notifications-service/src/main.js +++ /dev/null @@ -1,22 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const core_1 = require("@nestjs/core"); -const app_module_1 = require("./app.module"); -async function bootstrap() { - const app = await core_1.NestFactory.create(app_module_1.AppModule); - // Enable CORS - app.enableCors({ - origin: process.env.ALLOWED_ORIGINS?.split(',') || [ - 'http://localhost:3000', - /\.shapeshift\.com$/, - ], - credentials: true, - }); - app.getHttpAdapter().get('/health', (_, res) => { - res.status(200).json({ status: 'ok' }); - }); - const port = process.env.PORT || 3000; - await app.listen(port); - console.log(`Notifications service is running on: http://localhost:${port}`); -} -bootstrap(); diff --git a/apps/notifications-service/src/notifications/notifications.controller.js b/apps/notifications-service/src/notifications/notifications.controller.js deleted file mode 100644 index 5c84754..0000000 --- a/apps/notifications-service/src/notifications/notifications.controller.js +++ /dev/null @@ -1,88 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); -}; -var __param = (this && this.__param) || function (paramIndex, decorator) { - return function (target, key) { decorator(target, key, paramIndex); } -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.NotificationsController = void 0; -const common_1 = require("@nestjs/common"); -const notifications_service_1 = require("./notifications.service"); -let NotificationsController = class NotificationsController { - constructor(notificationsService) { - this.notificationsService = notificationsService; - } - async createNotification(data) { - return this.notificationsService.createNotification(data); - } - async registerDevice(data) { - return this.notificationsService.registerDevice(data.userId, data.deviceToken, data.deviceType); - } - async getUserNotifications(userId, limit) { - return this.notificationsService.getUserNotifications(userId, limit ? parseInt(limit) : 50); - } - async getUserDevices(userId) { - return this.notificationsService.getUserDevices(userId); - } - async sendToUser(data) { - return this.notificationsService.sendPushNotificationToUser(data.userId, data.title, data.body, data.data); - } - async sendToDevice(data) { - return this.notificationsService.sendPushNotificationToDevice(data.deviceToken, data.title, data.body, data.data); - } -}; -exports.NotificationsController = NotificationsController; -__decorate([ - (0, common_1.Post)(), - __param(0, (0, common_1.Body)()), - __metadata("design:type", Function), - __metadata("design:paramtypes", [Object]), - __metadata("design:returntype", Promise) -], NotificationsController.prototype, "createNotification", null); -__decorate([ - (0, common_1.Post)('register-device'), - __param(0, (0, common_1.Body)()), - __metadata("design:type", Function), - __metadata("design:paramtypes", [Object]), - __metadata("design:returntype", Promise) -], NotificationsController.prototype, "registerDevice", null); -__decorate([ - (0, common_1.Get)('user/:userId'), - __param(0, (0, common_1.Param)('userId')), - __param(1, (0, common_1.Query)('limit')), - __metadata("design:type", Function), - __metadata("design:paramtypes", [String, String]), - __metadata("design:returntype", Promise) -], NotificationsController.prototype, "getUserNotifications", null); -__decorate([ - (0, common_1.Get)('devices/:userId'), - __param(0, (0, common_1.Param)('userId')), - __metadata("design:type", Function), - __metadata("design:paramtypes", [String]), - __metadata("design:returntype", Promise) -], NotificationsController.prototype, "getUserDevices", null); -__decorate([ - (0, common_1.Post)('send-to-user'), - __param(0, (0, common_1.Body)()), - __metadata("design:type", Function), - __metadata("design:paramtypes", [Object]), - __metadata("design:returntype", Promise) -], NotificationsController.prototype, "sendToUser", null); -__decorate([ - (0, common_1.Post)('send-to-device'), - __param(0, (0, common_1.Body)()), - __metadata("design:type", Function), - __metadata("design:paramtypes", [Object]), - __metadata("design:returntype", Promise) -], NotificationsController.prototype, "sendToDevice", null); -exports.NotificationsController = NotificationsController = __decorate([ - (0, common_1.Controller)('notifications'), - __metadata("design:paramtypes", [notifications_service_1.NotificationsService]) -], NotificationsController); diff --git a/apps/notifications-service/src/notifications/notifications.service.js b/apps/notifications-service/src/notifications/notifications.service.js deleted file mode 100644 index 6d76762..0000000 --- a/apps/notifications-service/src/notifications/notifications.service.js +++ /dev/null @@ -1,201 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); -}; -var NotificationsService_1; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.NotificationsService = void 0; -const common_1 = require("@nestjs/common"); -const axios_1 = require("@nestjs/axios"); -const expo_server_sdk_1 = require("expo-server-sdk"); -const prisma_service_1 = require("../prisma/prisma.service"); -const shared_utils_1 = require("@shapeshift/shared-utils"); -let NotificationsService = NotificationsService_1 = class NotificationsService { - constructor(prisma, httpService) { - this.prisma = prisma; - this.httpService = httpService; - this.logger = new common_1.Logger(NotificationsService_1.name); - this.expo = new expo_server_sdk_1.Expo({ - accessToken: (0, shared_utils_1.getRequiredEnvVar)('EXPO_ACCESS_TOKEN'), - }); - } - async createNotification(data) { - try { - const notification = await this.prisma.notification.create({ - data: { - userId: data.userId, - title: data.title, - body: data.body, - type: data.type, - swapId: data.swapId, - }, - }); - // Send push notification to all user devices - await this.sendPushNotification(notification); - return notification; - } - catch (error) { - this.logger.error('Failed to create notification', error); - throw error; - } - } - async sendPushNotification(notification) { - try { - // Get user devices from user service - const userServiceUrl = (0, shared_utils_1.getRequiredEnvVar)('USER_SERVICE_URL'); - const response = await this.httpService.axiosRef.get(`${userServiceUrl}/users/${notification.userId}/devices`); - const devices = response.data; - const activeDevices = devices.filter((device) => device.isActive); - if (activeDevices.length === 0) { - throw new common_1.BadRequestException(`No active devices found for user ${notification.userId}`); - } - const messages = activeDevices - .filter((device) => device.deviceType === 'MOBILE') - .map((device) => ({ - to: device.deviceToken, - sound: 'default', - title: notification.title, - body: notification.body, - data: { - notificationId: notification.id, - type: notification.type, - swapId: notification.swapId, - }, - priority: 'high', - channelId: 'swap-notifications', - })); - const tickets = await this.sendExpoPushNotifications(messages, notification.id); - return tickets; - } - catch (error) { - this.logger.error('Failed to send push notification', error); - throw new common_1.BadRequestException('Failed to send push notification'); - } - } - async sendPushNotificationToDevice(deviceToken, title, body, data) { - try { - if (!expo_server_sdk_1.Expo.isExpoPushToken(deviceToken)) { - throw new common_1.BadRequestException(`Invalid Expo push token: ${String(deviceToken)}`); - } - const message = { - to: deviceToken, - sound: 'default', - title, - body, - data: data || {}, - priority: 'high', - channelId: 'swap-notifications', - }; - const tickets = await this.sendExpoPushNotifications([message]); - return tickets; - } - catch (error) { - this.logger.error('Failed to send push notification to device', error); - throw new common_1.BadRequestException('Failed to send push notification to device'); - } - } - async sendPushNotificationToUser(userId, title, body, data) { - try { - // Get user devices from user service - const userServiceUrl = (0, shared_utils_1.getRequiredEnvVar)('USER_SERVICE_URL'); - const response = await this.httpService.axiosRef.get(`${userServiceUrl}/users/${userId}/devices`); - const devices = response.data; - const activeDevices = devices.filter((device) => device.isActive); - if (activeDevices.length === 0) { - throw new common_1.BadRequestException(`No active devices found for user ${userId}`); - } - const messages = activeDevices.map((device) => ({ - to: device.deviceToken, - sound: 'default', - title, - body, - data: data || {}, - priority: 'high', - channelId: 'swap-notifications', - })); - const tickets = await this.sendExpoPushNotifications(messages); - return tickets; - } - catch { - throw new common_1.BadRequestException('Failed to send push notification to user'); - } - } - async sendExpoPushNotifications(messages, notificationId) { - const chunks = this.expo.chunkPushNotifications(messages); - const tickets = []; - for (const chunk of chunks) { - try { - const ticketChunk = await this.expo.sendPushNotificationsAsync(chunk); - tickets.push(...ticketChunk); - } - catch (error) { - this.logger.error('Error sending chunk', error); - } - } - // Update notification with delivery timestamp if notificationId is provided - if (notificationId) { - await this.prisma.notification.update({ - where: { id: notificationId }, - data: { deliveredAt: new Date() }, - }); - } - this.logger.log(`Sent ${tickets.length} push notifications`); - return tickets; - } - async registerDevice(userId, deviceToken, deviceType) { - try { - this.logger.log(`registerDevice called with userId: ${userId}, deviceType: ${deviceType}, deviceToken: ${deviceToken}`); - // Only validate Expo push token for mobile devices - if (deviceType === 'MOBILE' && !expo_server_sdk_1.Expo.isExpoPushToken(deviceToken)) { - throw new common_1.BadRequestException('Invalid Expo push token'); - } - // For web devices, we expect a websocket channel identifier - if (deviceType === 'WEB' && !deviceToken) { - throw new common_1.BadRequestException('Invalid websocket channel identifier'); - } - // Register device with user service - const userServiceUrl = (0, shared_utils_1.getRequiredEnvVar)('USER_SERVICE_URL'); - const response = await this.httpService.axiosRef.post(`${userServiceUrl}/users/${userId}/devices`, { - deviceToken, - deviceType, - }); - const device = response.data; - this.logger.log(`Device registered: ${deviceToken} for user ${userId} (${deviceType})`); - return device; - } - catch (error) { - this.logger.error('Failed to register device', error); - throw new common_1.BadRequestException('Failed to register device'); - } - } - async getUserNotifications(userId, limit = 50) { - return this.prisma.notification.findMany({ - where: { userId }, - orderBy: { sentAt: 'desc' }, - take: limit, - }); - } - async getUserDevices(userId) { - try { - const userServiceUrl = (0, shared_utils_1.getRequiredEnvVar)('USER_SERVICE_URL'); - const response = await this.httpService.axiosRef.get(`${userServiceUrl}/users/${userId}/devices`); - return response.data; - } - catch (error) { - this.logger.error('Failed to get user devices', error); - throw new common_1.BadRequestException('Failed to get user devices'); - } - } -}; -exports.NotificationsService = NotificationsService; -exports.NotificationsService = NotificationsService = NotificationsService_1 = __decorate([ - (0, common_1.Injectable)(), - __metadata("design:paramtypes", [prisma_service_1.PrismaService, - axios_1.HttpService]) -], NotificationsService); diff --git a/apps/notifications-service/src/prisma/prisma.service.js b/apps/notifications-service/src/prisma/prisma.service.js deleted file mode 100644 index 2b91c98..0000000 --- a/apps/notifications-service/src/prisma/prisma.service.js +++ /dev/null @@ -1,23 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.PrismaService = void 0; -const common_1 = require("@nestjs/common"); -const client_1 = require("@prisma/client"); -let PrismaService = class PrismaService extends client_1.PrismaClient { - async onModuleInit() { - await this.$connect(); - } - async onModuleDestroy() { - await this.$disconnect(); - } -}; -exports.PrismaService = PrismaService; -exports.PrismaService = PrismaService = __decorate([ - (0, common_1.Injectable)() -], PrismaService); diff --git a/apps/notifications-service/src/websocket/websocket.gateway.js b/apps/notifications-service/src/websocket/websocket.gateway.js deleted file mode 100644 index baad0e6..0000000 --- a/apps/notifications-service/src/websocket/websocket.gateway.js +++ /dev/null @@ -1,80 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); -}; -var __param = (this && this.__param) || function (paramIndex, decorator) { - return function (target, key) { decorator(target, key, paramIndex); } -}; -var WebsocketGateway_1; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.WebsocketGateway = void 0; -const websockets_1 = require("@nestjs/websockets"); -const socket_io_1 = require("socket.io"); -const common_1 = require("@nestjs/common"); -const notifications_service_1 = require("../notifications/notifications.service"); -let WebsocketGateway = WebsocketGateway_1 = class WebsocketGateway { - constructor(notificationsService) { - this.notificationsService = notificationsService; - this.logger = new common_1.Logger(WebsocketGateway_1.name); - this.connectedClients = new Map(); - } - handleConnection(client) { - this.logger.log(`Client connected: ${client.id}`); - } - handleDisconnect(client) { - this.logger.log(`Client disconnected: ${client.id}`); - if (client.userId) { - this.connectedClients.delete(client.userId); - } - } - async handleGetNotifications(data, client) { - if (!client.userId) { - return { error: 'Not authenticated' }; - } - try { - const notifications = await this.notificationsService.getUserNotifications(client.userId, data.limit || 50); - return { success: true, notifications }; - } - catch (error) { - this.logger.error('Failed to get notifications', error); - return { error: 'Failed to get notifications' }; - } - } - async sendNotificationToUser(userId, notification) { - const client = this.connectedClients.get(userId); - if (client) { - client.emit('notification', notification); - } - this.server.to(`user:${userId}`).emit('notification', notification); - } - broadcastToAll(event, data) { - this.server.emit(event, data); - } -}; -exports.WebsocketGateway = WebsocketGateway; -__decorate([ - (0, websockets_1.WebSocketServer)(), - __metadata("design:type", socket_io_1.Server) -], WebsocketGateway.prototype, "server", void 0); -__decorate([ - (0, websockets_1.SubscribeMessage)('getNotifications'), - __param(0, (0, websockets_1.MessageBody)()), - __param(1, (0, websockets_1.ConnectedSocket)()), - __metadata("design:type", Function), - __metadata("design:paramtypes", [Object, Object]), - __metadata("design:returntype", Promise) -], WebsocketGateway.prototype, "handleGetNotifications", null); -exports.WebsocketGateway = WebsocketGateway = WebsocketGateway_1 = __decorate([ - (0, websockets_1.WebSocketGateway)({ - cors: { - origin: '*', - }, - }), - __metadata("design:paramtypes", [notifications_service_1.NotificationsService]) -], WebsocketGateway); diff --git a/apps/swap-service/src/app.module.js b/apps/swap-service/src/app.module.js deleted file mode 100644 index c47adff..0000000 --- a/apps/swap-service/src/app.module.js +++ /dev/null @@ -1,53 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.AppModule = void 0; -const common_1 = require("@nestjs/common"); -const schedule_1 = require("@nestjs/schedule"); -const axios_1 = require("@nestjs/axios"); -const prisma_service_1 = require("./prisma/prisma.service"); -const swaps_controller_1 = require("./swaps/swaps.controller"); -const swaps_service_1 = require("./swaps/swaps.service"); -const swap_polling_service_1 = require("./polling/swap-polling.service"); -const swap_verification_service_1 = require("./verification/swap-verification.service"); -const websocket_gateway_1 = require("./websocket/websocket.gateway"); -const chain_adapter_init_service_1 = require("./lib/chain-adapter-init.service"); -const chain_adapter_manager_service_1 = require("./lib/chain-adapter-manager.service"); -const evm_service_1 = require("./lib/chain-adapters/evm.service"); -const utxo_service_1 = require("./lib/chain-adapters/utxo.service"); -const cosmos_sdk_service_1 = require("./lib/chain-adapters/cosmos-sdk.service"); -const solana_service_1 = require("./lib/chain-adapters/solana.service"); -const config_1 = require("@nestjs/config"); -let AppModule = class AppModule { -}; -exports.AppModule = AppModule; -exports.AppModule = AppModule = __decorate([ - (0, common_1.Module)({ - imports: [ - schedule_1.ScheduleModule.forRoot(), - axios_1.HttpModule, - config_1.ConfigModule.forRoot({ - envFilePath: '../../.env', - }), - ], - controllers: [swaps_controller_1.SwapsController], - providers: [ - prisma_service_1.PrismaService, - swaps_service_1.SwapsService, - swap_polling_service_1.SwapPollingService, - swap_verification_service_1.SwapVerificationService, - websocket_gateway_1.WebsocketGateway, - chain_adapter_init_service_1.ChainAdapterInitService, - chain_adapter_manager_service_1.ChainAdapterManagerService, - evm_service_1.EvmChainAdapterService, - utxo_service_1.UtxoChainAdapterService, - cosmos_sdk_service_1.CosmosSdkChainAdapterService, - solana_service_1.SolanaChainAdapterService, - ], - }) -], AppModule); diff --git a/apps/swap-service/src/lib/chain-adapter-init.service.js b/apps/swap-service/src/lib/chain-adapter-init.service.js deleted file mode 100644 index 7b476ca..0000000 --- a/apps/swap-service/src/lib/chain-adapter-init.service.js +++ /dev/null @@ -1,55 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); -}; -var ChainAdapterInitService_1; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.ChainAdapterInitService = void 0; -const common_1 = require("@nestjs/common"); -const chain_adapter_manager_service_1 = require("./chain-adapter-manager.service"); -const evm_service_1 = require("./chain-adapters/evm.service"); -const utxo_service_1 = require("./chain-adapters/utxo.service"); -const cosmos_sdk_service_1 = require("./chain-adapters/cosmos-sdk.service"); -const solana_service_1 = require("./chain-adapters/solana.service"); -let ChainAdapterInitService = ChainAdapterInitService_1 = class ChainAdapterInitService { - constructor(chainAdapterManagerService, evmChainAdapterService, utxoChainAdapterService, cosmosSdkChainAdapterService, solanaChainAdapterService) { - this.chainAdapterManagerService = chainAdapterManagerService; - this.evmChainAdapterService = evmChainAdapterService; - this.utxoChainAdapterService = utxoChainAdapterService; - this.cosmosSdkChainAdapterService = cosmosSdkChainAdapterService; - this.solanaChainAdapterService = solanaChainAdapterService; - this.logger = new common_1.Logger(ChainAdapterInitService_1.name); - } - initializeChainAdapters() { - this.logger.log('Initializing chain adapters...'); - try { - this.evmChainAdapterService.initializeEvmChainAdapters(); - this.utxoChainAdapterService.initializeUtxoChainAdapters(); - this.cosmosSdkChainAdapterService.initializeCosmosSdkChainAdapters(); - this.solanaChainAdapterService.initializeSolanaChainAdapter(); - this.logger.log('All chain adapters initialized successfully'); - } - catch (error) { - this.logger.error('Failed to initialize chain adapters:', error); - throw error; - } - } - getChainAdapterManager() { - return this.chainAdapterManagerService.getChainAdapterManager(); - } -}; -exports.ChainAdapterInitService = ChainAdapterInitService; -exports.ChainAdapterInitService = ChainAdapterInitService = ChainAdapterInitService_1 = __decorate([ - (0, common_1.Injectable)(), - __metadata("design:paramtypes", [chain_adapter_manager_service_1.ChainAdapterManagerService, - evm_service_1.EvmChainAdapterService, - utxo_service_1.UtxoChainAdapterService, - cosmos_sdk_service_1.CosmosSdkChainAdapterService, - solana_service_1.SolanaChainAdapterService]) -], ChainAdapterInitService); diff --git a/apps/swap-service/src/lib/chain-adapter-manager.service.js b/apps/swap-service/src/lib/chain-adapter-manager.service.js deleted file mode 100644 index 2e407df..0000000 --- a/apps/swap-service/src/lib/chain-adapter-manager.service.js +++ /dev/null @@ -1,25 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.ChainAdapterManagerService = void 0; -const common_1 = require("@nestjs/common"); -let ChainAdapterManagerService = class ChainAdapterManagerService { - constructor() { - this.chainAdapterManager = new Map(); - } - getChainAdapterManager() { - return this.chainAdapterManager; - } - setChainAdapterManager(manager) { - this.chainAdapterManager = manager; - } -}; -exports.ChainAdapterManagerService = ChainAdapterManagerService; -exports.ChainAdapterManagerService = ChainAdapterManagerService = __decorate([ - (0, common_1.Injectable)() -], ChainAdapterManagerService); diff --git a/apps/swap-service/src/lib/chain-adapters/cosmos-sdk.service.js b/apps/swap-service/src/lib/chain-adapters/cosmos-sdk.service.js deleted file mode 100644 index 228c61c..0000000 --- a/apps/swap-service/src/lib/chain-adapters/cosmos-sdk.service.js +++ /dev/null @@ -1,132 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); -}; -var CosmosSdkChainAdapterService_1; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.CosmosSdkChainAdapterService = void 0; -const common_1 = require("@nestjs/common"); -const chain_adapter_manager_service_1 = require("../chain-adapter-manager.service"); -const unchained = __importStar(require("@shapeshiftoss/unchained-client")); -const chain_adapters_1 = require("@shapeshiftoss/chain-adapters"); -const caip_1 = require("@shapeshiftoss/caip"); -const chain_adapters_2 = require("@shapeshiftoss/chain-adapters"); -let CosmosSdkChainAdapterService = CosmosSdkChainAdapterService_1 = class CosmosSdkChainAdapterService { - constructor(chainAdapterManagerService) { - this.chainAdapterManagerService = chainAdapterManagerService; - this.logger = new common_1.Logger(CosmosSdkChainAdapterService_1.name); - } - initializeCosmosSdkChainAdapters() { - this.logger.log('Initializing Cosmos SDK chain adapters...'); - const chainAdapterManager = this.chainAdapterManagerService.getChainAdapterManager(); - try { - this.initializeCosmosAdapter(chainAdapterManager); - this.initializeThorchainAdapter(chainAdapterManager); - this.initializeMayachainAdapter(chainAdapterManager); - this.logger.log('All Cosmos SDK chain adapters initialized successfully'); - } - catch (error) { - this.logger.error('Failed to initialize Cosmos SDK chain adapters:', error); - throw error; - } - } - initializeCosmosAdapter(chainAdapterManager) { - const cosmosHttp = new unchained.cosmos.V1Api(new unchained.cosmos.Configuration({ - basePath: process.env.VITE_UNCHAINED_COSMOS_HTTP_URL, - })); - const cosmosWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_COSMOS_WS_URL); - const cosmosAdapter = new chain_adapters_1.cosmos.ChainAdapter({ - providers: { http: cosmosHttp, ws: cosmosWs }, - midgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, - coinName: 'Cosmos', - }); - chainAdapterManager.set(caip_1.cosmosChainId, cosmosAdapter); - this.logger.log('Cosmos adapter initialized'); - } - initializeThorchainAdapter(chainAdapterManager) { - const http = new unchained.thorchain.V1Api(new unchained.thorchain.Configuration({ - basePath: process.env.VITE_UNCHAINED_THORCHAIN_HTTP_URL, - })); - const httpV1 = new unchained.thorchainV1.V1Api(new unchained.thorchainV1.Configuration({ - basePath: process.env.VITE_UNCHAINED_THORCHAIN_V1_HTTP_URL, - })); - const ws = new unchained.ws.Client(process.env.VITE_UNCHAINED_THORCHAIN_WS_URL); - const thorchainAdapter = new chain_adapters_1.thorchain.ChainAdapter({ - providers: { http, ws }, - thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, - mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, - coinName: 'THOR', - httpV1, - }); - chainAdapterManager.set(caip_1.thorchainChainId, thorchainAdapter); - this.logger.log('Thorchain adapter initialized'); - } - initializeMayachainAdapter(chainAdapterManager) { - const mayachainHttp = new unchained.mayachain.V1Api(new unchained.mayachain.Configuration({ - basePath: process.env.VITE_UNCHAINED_MAYACHAIN_HTTP_URL, - })); - const mayachainWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_MAYACHAIN_WS_URL); - const mayachainAdapter = new chain_adapters_1.mayachain.ChainAdapter({ - providers: { http: mayachainHttp, ws: mayachainWs }, - midgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, - coinName: 'MAYA', - }); - chainAdapterManager.set(caip_1.mayachainChainId, mayachainAdapter); - this.logger.log('Mayachain adapter initialized'); - } - assertGetCosmosSdkChainAdapter(chainId) { - if (!chain_adapters_2.cosmosSdkChainIds.includes(chainId)) { - throw new Error(`Chain ${chainId} is not a Cosmos SDK chain`); - } - const chainAdapterManager = this.chainAdapterManagerService.getChainAdapterManager(); - const adapter = chainAdapterManager.get(chainId); - if (!adapter) { - throw new Error(`Cosmos SDK chain adapter not found for chain ${chainId}`); - } - return adapter; - } -}; -exports.CosmosSdkChainAdapterService = CosmosSdkChainAdapterService; -exports.CosmosSdkChainAdapterService = CosmosSdkChainAdapterService = CosmosSdkChainAdapterService_1 = __decorate([ - (0, common_1.Injectable)(), - __metadata("design:paramtypes", [chain_adapter_manager_service_1.ChainAdapterManagerService]) -], CosmosSdkChainAdapterService); diff --git a/apps/swap-service/src/lib/chain-adapters/evm.service.js b/apps/swap-service/src/lib/chain-adapters/evm.service.js deleted file mode 100644 index cb4623a..0000000 --- a/apps/swap-service/src/lib/chain-adapters/evm.service.js +++ /dev/null @@ -1,220 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); -}; -var EvmChainAdapterService_1; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.EvmChainAdapterService = void 0; -const common_1 = require("@nestjs/common"); -const chain_adapter_manager_service_1 = require("../chain-adapter-manager.service"); -const unchained = __importStar(require("@shapeshiftoss/unchained-client")); -const chain_adapters_1 = require("@shapeshiftoss/chain-adapters"); -const caip_1 = require("@shapeshiftoss/caip"); -const chain_adapters_2 = require("@shapeshiftoss/chain-adapters"); -let EvmChainAdapterService = EvmChainAdapterService_1 = class EvmChainAdapterService { - constructor(chainAdapterManagerService) { - this.chainAdapterManagerService = chainAdapterManagerService; - this.logger = new common_1.Logger(EvmChainAdapterService_1.name); - } - async initializeEvmChainAdapters() { - this.logger.log('Initializing EVM chain adapters...'); - const chainAdapterManager = this.chainAdapterManagerService.getChainAdapterManager(); - try { - await this.initializeEthereumAdapter(chainAdapterManager); - await this.initializeAvalancheAdapter(chainAdapterManager); - await this.initializeOptimismAdapter(chainAdapterManager); - await this.initializeBscAdapter(chainAdapterManager); - await this.initializePolygonAdapter(chainAdapterManager); - await this.initializeGnosisAdapter(chainAdapterManager); - await this.initializeArbitrumAdapter(chainAdapterManager); - await this.initializeArbitrumNovaAdapter(chainAdapterManager); - await this.initializeBaseAdapter(chainAdapterManager); - this.logger.log('All EVM chain adapters initialized successfully'); - } - catch (error) { - this.logger.error('Failed to initialize EVM chain adapters:', error); - throw error; - } - } - async initializeEthereumAdapter(chainAdapterManager) { - const ethereumHttp = new unchained.ethereum.V1Api(new unchained.ethereum.Configuration({ - basePath: process.env.VITE_UNCHAINED_ETHEREUM_HTTP_URL, - })); - const ethereumWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_ETHEREUM_WS_URL); - const ethereumAdapter = new chain_adapters_1.ethereum.ChainAdapter({ - providers: { http: ethereumHttp, ws: ethereumWs }, - rpcUrl: process.env.VITE_ETHEREUM_NODE_URL, - thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, - mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, - }); - chainAdapterManager.set(caip_1.ethChainId, ethereumAdapter); - this.logger.log('Ethereum adapter initialized'); - } - async initializeAvalancheAdapter(chainAdapterManager) { - const avalancheHttp = new unchained.ethereum.V1Api(new unchained.ethereum.Configuration({ - basePath: process.env.VITE_UNCHAINED_AVALANCHE_HTTP_URL, - })); - const avalancheWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_AVALANCHE_WS_URL); - const avalancheAdapter = new chain_adapters_1.ethereum.ChainAdapter({ - providers: { http: avalancheHttp, ws: avalancheWs }, - rpcUrl: process.env.VITE_AVALANCHE_NODE_URL, - thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, - mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, - }); - chainAdapterManager.set(caip_1.avalancheChainId, avalancheAdapter); - this.logger.log('Avalanche adapter initialized'); - } - async initializeOptimismAdapter(chainAdapterManager) { - const optimismHttp = new unchained.ethereum.V1Api(new unchained.ethereum.Configuration({ - basePath: process.env.VITE_UNCHAINED_OPTIMISM_HTTP_URL, - })); - const optimismWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_OPTIMISM_WS_URL); - const optimismAdapter = new chain_adapters_1.ethereum.ChainAdapter({ - providers: { http: optimismHttp, ws: optimismWs }, - rpcUrl: process.env.VITE_OPTIMISM_NODE_URL, - thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, - mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, - }); - chainAdapterManager.set(caip_1.optimismChainId, optimismAdapter); - this.logger.log('Optimism adapter initialized'); - } - async initializeBscAdapter(chainAdapterManager) { - const bscHttp = new unchained.ethereum.V1Api(new unchained.ethereum.Configuration({ - basePath: process.env.VITE_UNCHAINED_BNBSMARTCHAIN_HTTP_URL, - })); - const bscWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_BNBSMARTCHAIN_WS_URL); - const bscAdapter = new chain_adapters_1.ethereum.ChainAdapter({ - providers: { http: bscHttp, ws: bscWs }, - rpcUrl: process.env.VITE_BNBSMARTCHAIN_NODE_URL, - thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, - mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, - }); - chainAdapterManager.set(caip_1.bscChainId, bscAdapter); - this.logger.log('BNB Smart Chain adapter initialized'); - } - async initializePolygonAdapter(chainAdapterManager) { - const polygonHttp = new unchained.ethereum.V1Api(new unchained.ethereum.Configuration({ - basePath: process.env.VITE_UNCHAINED_POLYGON_HTTP_URL, - })); - const polygonWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_POLYGON_WS_URL); - const polygonAdapter = new chain_adapters_1.ethereum.ChainAdapter({ - providers: { http: polygonHttp, ws: polygonWs }, - rpcUrl: process.env.VITE_POLYGON_NODE_URL, - thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, - mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, - }); - chainAdapterManager.set(caip_1.polygonChainId, polygonAdapter); - this.logger.log('Polygon adapter initialized'); - } - async initializeGnosisAdapter(chainAdapterManager) { - const gnosisHttp = new unchained.ethereum.V1Api(new unchained.ethereum.Configuration({ - basePath: process.env.VITE_UNCHAINED_GNOSIS_HTTP_URL, - })); - const gnosisWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_GNOSIS_WS_URL); - const gnosisAdapter = new chain_adapters_1.ethereum.ChainAdapter({ - providers: { http: gnosisHttp, ws: gnosisWs }, - rpcUrl: process.env.VITE_GNOSIS_NODE_URL, - thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, - mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, - }); - chainAdapterManager.set(caip_1.gnosisChainId, gnosisAdapter); - this.logger.log('Gnosis adapter initialized'); - } - async initializeArbitrumAdapter(chainAdapterManager) { - const arbitrumHttp = new unchained.ethereum.V1Api(new unchained.ethereum.Configuration({ - basePath: process.env.VITE_UNCHAINED_ARBITRUM_HTTP_URL, - })); - const arbitrumWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_ARBITRUM_WS_URL); - const arbitrumAdapter = new chain_adapters_1.ethereum.ChainAdapter({ - providers: { http: arbitrumHttp, ws: arbitrumWs }, - rpcUrl: process.env.VITE_ARBITRUM_NODE_URL, - thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, - mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, - }); - chainAdapterManager.set(caip_1.arbitrumChainId, arbitrumAdapter); - this.logger.log('Arbitrum adapter initialized'); - } - async initializeArbitrumNovaAdapter(chainAdapterManager) { - const arbitrumNovaHttp = new unchained.ethereum.V1Api(new unchained.ethereum.Configuration({ - basePath: process.env.VITE_UNCHAINED_ARBITRUM_NOVA_HTTP_URL, - })); - const arbitrumNovaWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_ARBITRUM_NOVA_WS_URL); - const arbitrumNovaAdapter = new chain_adapters_1.ethereum.ChainAdapter({ - providers: { http: arbitrumNovaHttp, ws: arbitrumNovaWs }, - rpcUrl: process.env.VITE_ARBITRUM_NOVA_NODE_URL, - thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, - mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, - }); - chainAdapterManager.set(caip_1.arbitrumNovaChainId, arbitrumNovaAdapter); - this.logger.log('Arbitrum Nova adapter initialized'); - } - async initializeBaseAdapter(chainAdapterManager) { - const baseHttp = new unchained.ethereum.V1Api(new unchained.ethereum.Configuration({ - basePath: process.env.VITE_UNCHAINED_BASE_HTTP_URL, - })); - const baseWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_BASE_WS_URL); - const baseAdapter = new chain_adapters_1.ethereum.ChainAdapter({ - providers: { http: baseHttp, ws: baseWs }, - rpcUrl: process.env.VITE_BASE_NODE_URL, - thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, - mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, - }); - chainAdapterManager.set(caip_1.baseChainId, baseAdapter); - this.logger.log('Base adapter initialized'); - } - isEvmChainAdapter(chainAdapter) { - return chain_adapters_2.evmChainIds.includes(chainAdapter.getChainId()); - } - assertGetEvmChainAdapter(chainId) { - const chainAdapterManager = this.chainAdapterManagerService.getChainAdapterManager(); - const adapter = chainAdapterManager.get(chainId); - if (!this.isEvmChainAdapter(adapter)) { - throw Error('invalid chain adapter'); - } - return adapter; - } -}; -exports.EvmChainAdapterService = EvmChainAdapterService; -exports.EvmChainAdapterService = EvmChainAdapterService = EvmChainAdapterService_1 = __decorate([ - (0, common_1.Injectable)(), - __metadata("design:paramtypes", [chain_adapter_manager_service_1.ChainAdapterManagerService]) -], EvmChainAdapterService); diff --git a/apps/swap-service/src/lib/chain-adapters/solana.service.js b/apps/swap-service/src/lib/chain-adapters/solana.service.js deleted file mode 100644 index c213ec7..0000000 --- a/apps/swap-service/src/lib/chain-adapters/solana.service.js +++ /dev/null @@ -1,94 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); -}; -var SolanaChainAdapterService_1; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.SolanaChainAdapterService = void 0; -const common_1 = require("@nestjs/common"); -const chain_adapter_manager_service_1 = require("../chain-adapter-manager.service"); -const unchained = __importStar(require("@shapeshiftoss/unchained-client")); -const chain_adapters_1 = require("@shapeshiftoss/chain-adapters"); -const caip_1 = require("@shapeshiftoss/caip"); -let SolanaChainAdapterService = SolanaChainAdapterService_1 = class SolanaChainAdapterService { - constructor(chainAdapterManagerService) { - this.chainAdapterManagerService = chainAdapterManagerService; - this.logger = new common_1.Logger(SolanaChainAdapterService_1.name); - } - initializeSolanaChainAdapter() { - this.logger.log('Initializing Solana chain adapter...'); - const chainAdapterManager = this.chainAdapterManagerService.getChainAdapterManager(); - try { - this.initializeSolanaAdapter(chainAdapterManager); - this.logger.log('Solana chain adapter initialized successfully'); - } - catch (error) { - this.logger.error('Failed to initialize Solana chain adapter:', error); - throw error; - } - } - initializeSolanaAdapter(chainAdapterManager) { - const solanaHttp = new unchained.solana.V1Api(new unchained.solana.Configuration({ - basePath: process.env.VITE_UNCHAINED_SOLANA_HTTP_URL, - })); - const solanaWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_SOLANA_WS_URL); - const solanaAdapter = new chain_adapters_1.solana.ChainAdapter({ - providers: { http: solanaHttp, ws: solanaWs }, - rpcUrl: process.env.VITE_SOLANA_NODE_URL, - }); - chainAdapterManager.set(caip_1.solanaChainId, solanaAdapter); - this.logger.log('Solana adapter initialized'); - } - assertGetSolanaChainAdapter(chainId) { - const chainAdapterManager = this.chainAdapterManagerService.getChainAdapterManager(); - const adapter = chainAdapterManager.get(chainId); - if (!adapter) { - throw new Error(`Solana chain adapter not found for chain ${chainId}`); - } - return adapter; - } -}; -exports.SolanaChainAdapterService = SolanaChainAdapterService; -exports.SolanaChainAdapterService = SolanaChainAdapterService = SolanaChainAdapterService_1 = __decorate([ - (0, common_1.Injectable)(), - __metadata("design:paramtypes", [chain_adapter_manager_service_1.ChainAdapterManagerService]) -], SolanaChainAdapterService); diff --git a/apps/swap-service/src/lib/chain-adapters/utxo.service.js b/apps/swap-service/src/lib/chain-adapters/utxo.service.js deleted file mode 100644 index acfadaf..0000000 --- a/apps/swap-service/src/lib/chain-adapters/utxo.service.js +++ /dev/null @@ -1,145 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); -}; -var UtxoChainAdapterService_1; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.UtxoChainAdapterService = void 0; -const common_1 = require("@nestjs/common"); -const chain_adapter_manager_service_1 = require("../chain-adapter-manager.service"); -const unchained = __importStar(require("@shapeshiftoss/unchained-client")); -const chain_adapters_1 = require("@shapeshiftoss/chain-adapters"); -const caip_1 = require("@shapeshiftoss/caip"); -const chain_adapters_2 = require("@shapeshiftoss/chain-adapters"); -let UtxoChainAdapterService = UtxoChainAdapterService_1 = class UtxoChainAdapterService { - constructor(chainAdapterManagerService) { - this.chainAdapterManagerService = chainAdapterManagerService; - this.logger = new common_1.Logger(UtxoChainAdapterService_1.name); - } - async initializeUtxoChainAdapters() { - this.logger.log('Initializing UTXO chain adapters...'); - const chainAdapterManager = this.chainAdapterManagerService.getChainAdapterManager(); - try { - await this.initializeBitcoinAdapter(chainAdapterManager); - await this.initializeBitcoinCashAdapter(chainAdapterManager); - await this.initializeDogecoinAdapter(chainAdapterManager); - await this.initializeLitecoinAdapter(chainAdapterManager); - this.logger.log('All UTXO chain adapters initialized successfully'); - } - catch (error) { - this.logger.error('Failed to initialize UTXO chain adapters:', error); - throw error; - } - } - async initializeBitcoinAdapter(chainAdapterManager) { - const bitcoinHttp = new unchained.bitcoin.V1Api(new unchained.bitcoin.Configuration({ - basePath: process.env.VITE_UNCHAINED_BITCOIN_HTTP_URL, - })); - const bitcoinWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_BITCOIN_WS_URL); - const bitcoinAdapter = new chain_adapters_1.bitcoin.ChainAdapter({ - providers: { http: bitcoinHttp, ws: bitcoinWs }, - coinName: 'Bitcoin', - thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, - mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, - }); - chainAdapterManager.set(caip_1.btcChainId, bitcoinAdapter); - this.logger.log('Bitcoin adapter initialized'); - } - async initializeBitcoinCashAdapter(chainAdapterManager) { - const bchHttp = new unchained.bitcoin.V1Api(new unchained.bitcoin.Configuration({ - basePath: process.env.VITE_UNCHAINED_BITCOINCASH_HTTP_URL, - })); - const bchWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_BITCOINCASH_WS_URL); - const bchAdapter = new chain_adapters_1.bitcoin.ChainAdapter({ - providers: { http: bchHttp, ws: bchWs }, - coinName: 'BitcoinCash', - thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, - mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, - }); - chainAdapterManager.set(caip_1.bchChainId, bchAdapter); - this.logger.log('Bitcoin Cash adapter initialized'); - } - async initializeDogecoinAdapter(chainAdapterManager) { - const dogeHttp = new unchained.bitcoin.V1Api(new unchained.bitcoin.Configuration({ - basePath: process.env.VITE_UNCHAINED_DOGECOIN_HTTP_URL, - })); - const dogeWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_DOGECOIN_WS_URL); - const dogeAdapter = new chain_adapters_1.bitcoin.ChainAdapter({ - providers: { http: dogeHttp, ws: dogeWs }, - coinName: 'Dogecoin', - thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, - mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, - }); - chainAdapterManager.set(caip_1.dogeChainId, dogeAdapter); - this.logger.log('Dogecoin adapter initialized'); - } - async initializeLitecoinAdapter(chainAdapterManager) { - const ltcHttp = new unchained.bitcoin.V1Api(new unchained.bitcoin.Configuration({ - basePath: process.env.VITE_UNCHAINED_LITECOIN_HTTP_URL, - })); - const ltcWs = new unchained.ws.Client(process.env.VITE_UNCHAINED_LITECOIN_WS_URL); - const ltcAdapter = new chain_adapters_1.bitcoin.ChainAdapter({ - providers: { http: ltcHttp, ws: ltcWs }, - coinName: 'Litecoin', - thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, - mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, - }); - chainAdapterManager.set(caip_1.ltcChainId, ltcAdapter); - this.logger.log('Litecoin adapter initialized'); - } - assertGetUtxoChainAdapter(chainId) { - if (!chain_adapters_2.utxoChainIds.includes(chainId)) { - throw new Error(`Chain ${chainId} is not a UTXO chain`); - } - const chainAdapterManager = this.chainAdapterManagerService.getChainAdapterManager(); - const adapter = chainAdapterManager.get(chainId); - if (!adapter) { - throw new Error(`UTXO chain adapter not found for chain ${chainId}`); - } - return adapter; - } -}; -exports.UtxoChainAdapterService = UtxoChainAdapterService; -exports.UtxoChainAdapterService = UtxoChainAdapterService = UtxoChainAdapterService_1 = __decorate([ - (0, common_1.Injectable)(), - __metadata("design:paramtypes", [chain_adapter_manager_service_1.ChainAdapterManagerService]) -], UtxoChainAdapterService); diff --git a/apps/swap-service/src/main.js b/apps/swap-service/src/main.js deleted file mode 100644 index 45bdf39..0000000 --- a/apps/swap-service/src/main.js +++ /dev/null @@ -1,26 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const core_1 = require("@nestjs/core"); -const app_module_1 = require("./app.module"); -const chain_adapter_init_service_1 = require("./lib/chain-adapter-init.service"); -async function bootstrap() { - const app = await core_1.NestFactory.create(app_module_1.AppModule); - // Initialize chain adapters - const chainAdapterInitService = app.get(chain_adapter_init_service_1.ChainAdapterInitService); - await chainAdapterInitService.initializeChainAdapters(); - // Enable CORS - app.enableCors({ - origin: process.env.ALLOWED_ORIGINS?.split(',') || [ - 'http://localhost:3000', - /\.shapeshift\.com$/, - ], - credentials: true, - }); - app.getHttpAdapter().get('/health', (_, res) => { - res.status(200).json({ status: 'ok' }); - }); - const port = process.env.PORT || 3000; - await app.listen(port); - console.log(`Swap service is running on: http://localhost:${port}`); -} -bootstrap(); diff --git a/apps/swap-service/src/polling/swap-polling.service.js b/apps/swap-service/src/polling/swap-polling.service.js deleted file mode 100644 index de58f9e..0000000 --- a/apps/swap-service/src/polling/swap-polling.service.js +++ /dev/null @@ -1,69 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); -}; -var SwapPollingService_1; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.SwapPollingService = void 0; -const common_1 = require("@nestjs/common"); -const schedule_1 = require("@nestjs/schedule"); -const swaps_service_1 = require("../swaps/swaps.service"); -const websocket_gateway_1 = require("../websocket/websocket.gateway"); -let SwapPollingService = SwapPollingService_1 = class SwapPollingService { - constructor(swapsService, websocketGateway) { - this.swapsService = swapsService; - this.websocketGateway = websocketGateway; - this.logger = new common_1.Logger(SwapPollingService_1.name); - } - async pollPendingSwaps() { - try { - this.logger.log('Starting to poll pending swaps...'); - const pendingSwaps = await this.swapsService.getPendingSwaps(); - if (pendingSwaps.length === 0) { - this.logger.log('No pending swaps found'); - return; - } - this.logger.log(`Found ${pendingSwaps.length} pending swaps to poll`); - for (const swap of pendingSwaps) { - try { - const statusUpdate = await this.swapsService.pollSwapStatus(swap.swapId); - if (statusUpdate.status !== swap.status) { - this.logger.log(`Status changed for swap ${swap.swapId}: ${swap.status} -> ${statusUpdate.status}`); - const updatedSwap = await this.swapsService.updateSwapStatus({ - swapId: swap.swapId, - status: statusUpdate.status, - sellTxHash: statusUpdate.sellTxHash, - buyTxHash: statusUpdate.buyTxHash, - statusMessage: statusUpdate.statusMessage, - }); - await this.websocketGateway.sendSwapUpdateToUser(swap.userId, updatedSwap); - } - } - catch (error) { - this.logger.error(`Failed to poll swap ${swap.swapId}:`, error); - } - } - } - catch (error) { - this.logger.error('Failed to poll pending swaps:', error); - } - } -}; -exports.SwapPollingService = SwapPollingService; -__decorate([ - (0, schedule_1.Cron)(schedule_1.CronExpression.EVERY_5_SECONDS), - __metadata("design:type", Function), - __metadata("design:paramtypes", []), - __metadata("design:returntype", Promise) -], SwapPollingService.prototype, "pollPendingSwaps", null); -exports.SwapPollingService = SwapPollingService = SwapPollingService_1 = __decorate([ - (0, common_1.Injectable)(), - __metadata("design:paramtypes", [swaps_service_1.SwapsService, - websocket_gateway_1.WebsocketGateway]) -], SwapPollingService); diff --git a/apps/swap-service/src/prisma/prisma.service.js b/apps/swap-service/src/prisma/prisma.service.js deleted file mode 100644 index 2b91c98..0000000 --- a/apps/swap-service/src/prisma/prisma.service.js +++ /dev/null @@ -1,23 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.PrismaService = void 0; -const common_1 = require("@nestjs/common"); -const client_1 = require("@prisma/client"); -let PrismaService = class PrismaService extends client_1.PrismaClient { - async onModuleInit() { - await this.$connect(); - } - async onModuleDestroy() { - await this.$disconnect(); - } -}; -exports.PrismaService = PrismaService; -exports.PrismaService = PrismaService = __decorate([ - (0, common_1.Injectable)() -], PrismaService); diff --git a/apps/swap-service/src/swaps/swaps.controller.js b/apps/swap-service/src/swaps/swaps.controller.js deleted file mode 100644 index 5f67681..0000000 --- a/apps/swap-service/src/swaps/swaps.controller.js +++ /dev/null @@ -1,162 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); -}; -var __param = (this && this.__param) || function (paramIndex, decorator) { - return function (target, key) { decorator(target, key, paramIndex); } -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.SwapsController = exports.Prisma = exports.Swap = void 0; -const common_1 = require("@nestjs/common"); -const swaps_service_1 = require("./swaps.service"); -const swap_polling_service_1 = require("../polling/swap-polling.service"); -const swap_verification_service_1 = require("../verification/swap-verification.service"); -var client_1 = require("@prisma/client"); -Object.defineProperty(exports, "Swap", { enumerable: true, get: function () { return client_1.Swap; } }); -Object.defineProperty(exports, "Prisma", { enumerable: true, get: function () { return client_1.Prisma; } }); -let SwapsController = class SwapsController { - constructor(swapsService, swapPollingService, swapVerificationService) { - this.swapsService = swapsService; - this.swapPollingService = swapPollingService; - this.swapVerificationService = swapVerificationService; - } - async createSwap(data) { - return this.swapsService.createSwap(data); - } - async updateSwapStatus(swapId, data) { - return this.swapsService.updateSwapStatus({ - swapId, - ...data, - }); - } - async getSwapsByUser(userId, limit) { - return this.swapsService.getSwapsByUser(userId, limit ? parseInt(limit) : 50); - } - async getSwapsByAccountId(accountId) { - return this.swapsService.getSwapsByAccountId(accountId); - } - async getPendingSwaps() { - return this.swapsService.getPendingSwaps(); - } - async getReferralFees(referralCode, startDate, endDate) { - const start = startDate ? new Date(startDate) : undefined; - const end = endDate ? new Date(endDate) : undefined; - return this.swapsService.calculateReferralFees(referralCode, start, end); - } - async getAffiliateFees(affiliateAddress, startDate, endDate) { - const start = startDate ? new Date(startDate) : undefined; - const end = endDate ? new Date(endDate) : undefined; - return this.swapsService.calculateAffiliateFees(affiliateAddress, start, end); - } - async getSwap(swapId) { - const swap = await this.swapsService['prisma'].swap.findUnique({ - where: { swapId }, - }); - if (!swap) { - return null; - } - return { - ...swap, - sellAsset: swap.sellAsset, - buyAsset: swap.buyAsset, - }; - } - async verifySwapAffiliate(swapId, data) { - // Fetch the swap to get metadata and other details - const swap = await this.swapsService['prisma'].swap.findUnique({ - where: { swapId }, - }); - if (!swap) { - return { - isVerified: false, - hasAffiliate: false, - protocol: data.protocol, - swapId, - error: 'Swap not found', - }; - } - return this.swapVerificationService.verifySwapAffiliate(swapId, data.protocol || swap.swapperName, swap.sellAsset.chainId, data.txHash || swap.sellTxHash || undefined, swap.metadata); - } -}; -exports.SwapsController = SwapsController; -__decorate([ - (0, common_1.Post)(), - __param(0, (0, common_1.Body)()), - __metadata("design:type", Function), - __metadata("design:paramtypes", [Object]), - __metadata("design:returntype", Promise) -], SwapsController.prototype, "createSwap", null); -__decorate([ - (0, common_1.Put)(':swapId/status'), - __param(0, (0, common_1.Param)('swapId')), - __param(1, (0, common_1.Body)()), - __metadata("design:type", Function), - __metadata("design:paramtypes", [String, Object]), - __metadata("design:returntype", Promise) -], SwapsController.prototype, "updateSwapStatus", null); -__decorate([ - (0, common_1.Get)('user/:userId'), - __param(0, (0, common_1.Param)('userId')), - __param(1, (0, common_1.Query)('limit')), - __metadata("design:type", Function), - __metadata("design:paramtypes", [String, String]), - __metadata("design:returntype", Promise) -], SwapsController.prototype, "getSwapsByUser", null); -__decorate([ - (0, common_1.Get)('account/:accountId'), - __param(0, (0, common_1.Param)('accountId')), - __metadata("design:type", Function), - __metadata("design:paramtypes", [String]), - __metadata("design:returntype", Promise) -], SwapsController.prototype, "getSwapsByAccountId", null); -__decorate([ - (0, common_1.Get)('pending'), - __metadata("design:type", Function), - __metadata("design:paramtypes", []), - __metadata("design:returntype", Promise) -], SwapsController.prototype, "getPendingSwaps", null); -__decorate([ - (0, common_1.Get)('referral-fees/:referralCode'), - __param(0, (0, common_1.Param)('referralCode')), - __param(1, (0, common_1.Query)('startDate')), - __param(2, (0, common_1.Query)('endDate')), - __metadata("design:type", Function), - __metadata("design:paramtypes", [String, String, String]), - __metadata("design:returntype", Promise) -], SwapsController.prototype, "getReferralFees", null); -__decorate([ - (0, common_1.Get)('affiliate-fees/:affiliateAddress'), - __param(0, (0, common_1.Param)('affiliateAddress')), - __param(1, (0, common_1.Query)('startDate')), - __param(2, (0, common_1.Query)('endDate')), - __metadata("design:type", Function), - __metadata("design:paramtypes", [String, String, String]), - __metadata("design:returntype", Promise) -], SwapsController.prototype, "getAffiliateFees", null); -__decorate([ - (0, common_1.Get)(':swapId'), - __param(0, (0, common_1.Param)('swapId')), - __metadata("design:type", Function), - __metadata("design:paramtypes", [String]), - __metadata("design:returntype", Promise) -], SwapsController.prototype, "getSwap", null); -__decorate([ - (0, common_1.Post)(':swapId/verify-affiliate'), - __param(0, (0, common_1.Param)('swapId')), - __param(1, (0, common_1.Body)()), - __metadata("design:type", Function), - __metadata("design:paramtypes", [String, Object]), - __metadata("design:returntype", Promise) -], SwapsController.prototype, "verifySwapAffiliate", null); -exports.SwapsController = SwapsController = __decorate([ - (0, common_1.Controller)('swaps'), - __metadata("design:paramtypes", [swaps_service_1.SwapsService, - swap_polling_service_1.SwapPollingService, - swap_verification_service_1.SwapVerificationService]) -], SwapsController); diff --git a/apps/swap-service/src/swaps/swaps.service.js b/apps/swap-service/src/swaps/swaps.service.js deleted file mode 100644 index 700a3ca..0000000 --- a/apps/swap-service/src/swaps/swaps.service.js +++ /dev/null @@ -1,708 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); -}; -var SwapsService_1; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.SwapsService = void 0; -const common_1 = require("@nestjs/common"); -const prisma_service_1 = require("../prisma/prisma.service"); -const evm_service_1 = require("../lib/chain-adapters/evm.service"); -const utxo_service_1 = require("../lib/chain-adapters/utxo.service"); -const cosmos_sdk_service_1 = require("../lib/chain-adapters/cosmos-sdk.service"); -const solana_service_1 = require("../lib/chain-adapters/solana.service"); -const swap_verification_service_1 = require("../verification/swap-verification.service"); -const swapper_1 = require("@shapeshiftoss/swapper"); -const shared_utils_1 = require("@shapeshift/shared-utils"); -const affiliateFeeAsset_1 = require("../utils/affiliateFeeAsset"); -const shared_utils_2 = require("@shapeshift/shared-utils"); -const chain_adapters_1 = require("@shapeshiftoss/chain-adapters"); -const unchained_client_1 = require("@shapeshiftoss/unchained-client"); -let SwapsService = SwapsService_1 = class SwapsService { - constructor(prisma, evmChainAdapterService, utxoChainAdapterService, cosmosSdkChainAdapterService, solanaChainAdapterService, swapVerificationService) { - this.prisma = prisma; - this.evmChainAdapterService = evmChainAdapterService; - this.utxoChainAdapterService = utxoChainAdapterService; - this.cosmosSdkChainAdapterService = cosmosSdkChainAdapterService; - this.solanaChainAdapterService = solanaChainAdapterService; - this.swapVerificationService = swapVerificationService; - this.logger = new common_1.Logger(SwapsService_1.name); - this.notificationsClient = new shared_utils_2.NotificationsServiceClient(); - this.userServiceClient = new shared_utils_2.UserServiceClient(); - } - async createSwap(data) { - try { - let referralCode = null; - if (data.userId) { - try { - referralCode = await this.userServiceClient.getUserReferralCode(data.userId); - if (referralCode) { - this.logger.log(`Found referral code ${referralCode} for user ${data.userId}`); - } - } - catch (error) { - this.logger.warn(`Failed to fetch referral code for user ${data.userId}:`, error); - } - } - let sellAmountUsd = null; - try { - const { getAssetPriceUsd, calculateUsdValue } = await Promise.resolve().then(() => __importStar(require('../utils/pricing'))); - const price = await getAssetPriceUsd(data.sellAsset); - if (price) { - sellAmountUsd = calculateUsdValue(data.sellAmountCryptoPrecision, price); - } - } - catch (error) { - this.logger.warn(`Failed to calculate sellAmountUsd for swap ${data.swapId}:`, error); - } - const affiliateFeeAssetId = (0, affiliateFeeAsset_1.resolveAffiliateFeeAssetId)(data.swapperName, data.sellAsset, data.buyAsset); - const swap = await this.prisma.swap.create({ - data: { - swapId: data.swapId, - sellAsset: data.sellAsset, - buyAsset: data.buyAsset, - sellTxHash: data.sellTxHash || null, - sellAmountCryptoBaseUnit: data.sellAmountCryptoBaseUnit, - expectedBuyAmountCryptoBaseUnit: data.expectedBuyAmountCryptoBaseUnit, - sellAmountCryptoPrecision: data.sellAmountCryptoPrecision, - expectedBuyAmountCryptoPrecision: data.expectedBuyAmountCryptoPrecision, - source: data.source, - swapperName: data.swapperName, - sellAccountId: data.sellAccountId - ? (0, shared_utils_1.hashAccountId)(data.sellAccountId) - : 'api', - buyAccountId: data.buyAccountId - ? (0, shared_utils_1.hashAccountId)(data.buyAccountId) - : null, - receiveAddress: data.receiveAddress, - isStreaming: data.isStreaming || false, - metadata: data.metadata || {}, - userId: data.userId || 'api', - referralCode, - sellAmountUsd, - affiliateAddress: data.affiliateAddress || null, - affiliateBps: data.affiliateBps || null, - origin: data.origin || null, - affiliateFeeAssetId, - }, - }); - this.logger.log(`Swap created: ${swap.id}` + - `${referralCode ? ` with referral code ${referralCode}` : ''}` + - `${data.affiliateAddress ? ` with affiliate ${data.affiliateAddress}` : ''}` + - `${sellAmountUsd ? ` ($${sellAmountUsd})` : ''}`); - return swap; - } - catch (error) { - this.logger.error('Failed to create swap', error); - throw error; - } - } - async updateSwapStatus(data) { - try { - const swap = await this.prisma.swap.update({ - where: { swapId: data.swapId }, - data: { - status: data.status, - sellTxHash: data.sellTxHash, - buyTxHash: data.buyTxHash, - txLink: data.txLink, - statusMessage: data.statusMessage, - actualBuyAmountCryptoPrecision: data.actualBuyAmountCryptoPrecision, - }, - }); - await this.sendStatusUpdateNotification(swap); - this.logger.log(`Swap status updated: ${swap.swapId} -> ${data.status}`); - return { - ...swap, - sellAsset: swap.sellAsset, - buyAsset: swap.buyAsset, - }; - } - catch (error) { - this.logger.error('Failed to update swap status', error); - throw error; - } - } - formatAmount(amount) { - // Convert to number with up to 8 decimals, then remove trailing zeros - const num = (0, chain_adapters_1.bnOrZero)(amount).toFixed(8); - // Remove trailing zeros and trailing decimal point - return num.replace(/\.?0+$/, ''); - } - async sendStatusUpdateNotification(swap) { - let title; - let body; - let type; - const sellAsset = swap.sellAsset; - const buyAsset = swap.buyAsset; - switch (swap.status) { - case 'SUCCESS': { - title = 'Swap Completed!'; - const buyAmount = this.formatAmount(swap.actualBuyAmountCryptoPrecision || - swap.expectedBuyAmountCryptoPrecision); - body = `Your swap of ${this.formatAmount(swap.sellAmountCryptoPrecision)} ${sellAsset.symbol} to ${buyAmount} ${buyAsset.symbol} is complete.`; - type = 'SWAP_COMPLETED'; - break; - } - case 'FAILED': - title = 'Swap Failed'; - body = `Your ${sellAsset.symbol} to ${buyAsset.symbol} swap has failed`; - type = 'SWAP_FAILED'; - break; - default: - return; - } - if (swap.status === 'FAILED' || swap.status === 'SUCCESS') { - await this.notificationsClient.createNotification({ - userId: swap.userId, - title, - body, - type, - swapId: swap.id, - }); - } - } - async getSwapsByUser(userId, limit = 50) { - const swaps = await this.prisma.swap.findMany({ - where: { userId }, - orderBy: { createdAt: 'desc' }, - take: limit, - }); - return swaps.map((swap) => ({ - ...swap, - sellAsset: swap.sellAsset, - buyAsset: swap.buyAsset, - })); - } - async getSwapsByAccountId(accountId) { - const hashedAccountId = (0, shared_utils_1.hashAccountId)(accountId); - const swaps = await this.prisma.swap.findMany({ - where: { - OR: [ - { sellAccountId: hashedAccountId }, - { buyAccountId: hashedAccountId }, - ], - }, - }); - return swaps.map((swap) => ({ - ...swap, - sellAsset: swap.sellAsset, - buyAsset: swap.buyAsset, - })); - } - async getPendingSwaps() { - const swaps = await this.prisma.swap.findMany({ - where: { - status: { - in: ['IDLE', 'PENDING'], - }, - sellTxHash: { not: null }, - }, - }); - return swaps.map((swap) => ({ - ...swap, - sellAsset: swap.sellAsset, - buyAsset: swap.buyAsset, - })); - } - async calculateReferralFees(referralCode, startDate, endDate) { - this.logger.log(`Calculating referral fees for code: ${referralCode}, period: ${startDate?.toISOString()} - ${endDate?.toISOString()}`); - // Fetch swaps for the current period - const periodWhereClause = { - referralCode, - isAffiliateVerified: true, - status: 'SUCCESS', - }; - if (startDate && endDate) { - periodWhereClause.createdAt = { - gte: startDate, - lte: endDate, - }; - } - const periodSwaps = await this.prisma.swap.findMany({ - where: periodWhereClause, - select: { - id: true, - swapId: true, - sellAsset: true, - sellAmountCryptoPrecision: true, - affiliateVerificationDetails: true, - createdAt: true, - }, - }); - // Fetch ALL swaps since the start (for total fees collected by referrer) - const allTimeSwaps = await this.prisma.swap.findMany({ - where: { - referralCode, - isAffiliateVerified: true, - status: 'SUCCESS', - }, - select: { - id: true, - swapId: true, - sellAsset: true, - sellAmountCryptoPrecision: true, - affiliateVerificationDetails: true, - createdAt: true, - }, - }); - this.logger.log(`Found ${periodSwaps.length} swaps for period, ${allTimeSwaps.length} swaps all-time for referral code ${referralCode}`); - let periodFeesUsd = 0; - let totalSwapVolumeUsd = 0; - const swapCount = periodSwaps.length; - // Import pricing utilities dynamically - const { getAssetPriceUsd, calculateUsdValue } = await Promise.resolve().then(() => __importStar(require('../utils/pricing'))); - // Fetch prices for all unique assets from both period and all-time swaps - const uniqueAssets = new Map(); - for (const swap of [...periodSwaps, ...allTimeSwaps]) { - const sellAsset = swap.sellAsset; - if (!uniqueAssets.has(sellAsset.assetId)) { - uniqueAssets.set(sellAsset.assetId, sellAsset); - } - } - // Fetch all prices in parallel - const pricePromises = Array.from(uniqueAssets.values()).map(async (asset) => { - const price = await getAssetPriceUsd(asset); - return { assetId: asset.assetId, price }; - }); - const prices = await Promise.all(pricePromises); - const priceMap = new Map(); - prices.forEach(({ assetId, price }) => { - priceMap.set(assetId, price); - }); - // Calculate period fees and volume - for (const swap of periodSwaps) { - const sellAsset = swap.sellAsset; - const price = priceMap.get(sellAsset.assetId); - if (!price) { - this.logger.warn(`No price found for asset ${sellAsset.assetId}, skipping swap ${swap.swapId}`); - continue; - } - const sellAmountUsd = parseFloat(calculateUsdValue(swap.sellAmountCryptoPrecision, price)); - totalSwapVolumeUsd += sellAmountUsd; - // Extract affiliateBps from verification details - const verificationDetails = swap.affiliateVerificationDetails; - const affiliateBps = verificationDetails?.affiliateBps; - if (affiliateBps && sellAmountUsd > 0) { - // Fee = (sellAmountUsd × affiliateBps) / 10,000 - const feeUsd = (sellAmountUsd * affiliateBps) / 10000; - periodFeesUsd += feeUsd; - } - } - // Calculate all-time fees (for totalFeesCollectedUsd which represents total referrer earnings) - let allTimeFeesUsd = 0; - for (const swap of allTimeSwaps) { - const sellAsset = swap.sellAsset; - const price = priceMap.get(sellAsset.assetId); - if (!price) - continue; - const sellAmountUsd = parseFloat(calculateUsdValue(swap.sellAmountCryptoPrecision, price)); - const verificationDetails = swap.affiliateVerificationDetails; - const affiliateBps = verificationDetails?.affiliateBps; - if (affiliateBps && sellAmountUsd > 0) { - const feeUsd = (sellAmountUsd * affiliateBps) / 10000; - allTimeFeesUsd += feeUsd; - } - } - // Calculate referrer's 10% commission - const periodReferrerCommissionUsd = periodFeesUsd * 0.1; - const allTimeReferrerCommissionUsd = allTimeFeesUsd * 0.1; - this.logger.log(`Referral fee calculation for ${referralCode}: ` + - `Period: ${swapCount} swaps, $${totalSwapVolumeUsd.toFixed(2)} volume, $${periodReferrerCommissionUsd.toFixed(2)} commission | ` + - `All-time: ${allTimeSwaps.length} swaps, $${allTimeReferrerCommissionUsd.toFixed(2)} total commission`); - return { - referralCode, - swapCount, - totalSwapVolumeUsd: totalSwapVolumeUsd.toFixed(2), - totalFeesCollectedUsd: allTimeReferrerCommissionUsd.toFixed(2), // Total referrer earnings all-time - referrerCommissionUsd: periodReferrerCommissionUsd.toFixed(2), // Period referrer earnings - periodStart: startDate?.toISOString(), - periodEnd: endDate?.toISOString(), - }; - } - getDistributionCutoff() { - const now = new Date(); - const cutoff = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 5)); - if (now >= cutoff) - return cutoff; - return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - 1, 5)); - } - async resolveSwapUsdValue(swap, priceMap, freezeCutoff, calculateUsdValue) { - const sellAsset = swap.sellAsset; - const isFrozen = swap.createdAt < freezeCutoff; - if (isFrozen && swap.sellAmountUsd) { - return parseFloat(swap.sellAmountUsd); - } - const price = priceMap.get(sellAsset.assetId); - if (!price) { - this.logger.warn(`No price found for asset ${sellAsset.assetId}, skipping swap ${swap.swapId}`); - return null; - } - const liveUsdValue = parseFloat(calculateUsdValue(swap.sellAmountCryptoPrecision, price)); - if (isFrozen && !swap.sellAmountUsd) { - await this.prisma.swap.update({ - where: { id: swap.id }, - data: { sellAmountUsd: liveUsdValue.toFixed(2) }, - }); - this.logger.log(`Froze sellAmountUsd=${liveUsdValue.toFixed(2)} for swap ${swap.swapId} (pre-distribution)`); - } - return liveUsdValue; - } - async calculateAffiliateFees(affiliateAddress, startDate, endDate) { - this.logger.log(`Calculating affiliate fees for address: ${affiliateAddress}, period: ${startDate?.toISOString()} - ${endDate?.toISOString()}`); - const freezeCutoff = this.getDistributionCutoff(); - this.logger.log(`Distribution freeze cutoff: ${freezeCutoff.toISOString()}`); - const periodWhereClause = { - affiliateAddress, - isAffiliateVerified: true, - status: 'SUCCESS', - }; - if (startDate && endDate) { - periodWhereClause.createdAt = { - gte: startDate, - lte: endDate, - }; - } - const swapSelect = { - id: true, - swapId: true, - swapperName: true, - sellAsset: true, - buyAsset: true, - sellAmountCryptoBaseUnit: true, - sellAmountCryptoPrecision: true, - expectedBuyAmountCryptoBaseUnit: true, - sellAmountUsd: true, - affiliateBps: true, - origin: true, - affiliateFeeAssetId: true, - affiliateFeeAmountCryptoBaseUnit: true, - affiliateVerificationDetails: true, - createdAt: true, - }; - const periodSwaps = await this.prisma.swap.findMany({ - where: periodWhereClause, - select: swapSelect, - }); - const allTimeSwaps = await this.prisma.swap.findMany({ - where: { - affiliateAddress, - isAffiliateVerified: true, - status: 'SUCCESS', - }, - select: swapSelect, - }); - this.logger.log(`Found ${periodSwaps.length} swaps for period, ${allTimeSwaps.length} swaps all-time for affiliate ${affiliateAddress}`); - const { getAssetPriceUsd, calculateUsdValue } = await Promise.resolve().then(() => __importStar(require('../utils/pricing'))); - const uniqueAssets = new Map(); - for (const swap of [...periodSwaps, ...allTimeSwaps]) { - const sellAsset = swap.sellAsset; - const buyAsset = swap.buyAsset; - if (!uniqueAssets.has(sellAsset.assetId)) { - uniqueAssets.set(sellAsset.assetId, sellAsset); - } - if (!uniqueAssets.has(buyAsset.assetId)) { - uniqueAssets.set(buyAsset.assetId, buyAsset); - } - } - const pricePromises = Array.from(uniqueAssets.values()).map(async (asset) => { - const price = await getAssetPriceUsd(asset); - return { assetId: asset.assetId, price }; - }); - const prices = await Promise.all(pricePromises); - const priceMap = new Map(); - prices.forEach(({ assetId, price }) => { - priceMap.set(assetId, price); - }); - let periodCommissionUsd = 0; - let totalSwapVolumeUsd = 0; - const swapCount = periodSwaps.length; - for (const swap of periodSwaps) { - const sellAmountUsd = await this.resolveSwapUsdValue(swap, priceMap, freezeCutoff, calculateUsdValue); - if (sellAmountUsd === null) - continue; - totalSwapVolumeUsd += sellAmountUsd; - const verificationDetails = swap.affiliateVerificationDetails; - const verifiedBps = verificationDetails?.affiliateBps; - if (verifiedBps && sellAmountUsd > 0) { - const commissionRate = this.getAffiliateCommissionRate(swap.origin, verifiedBps); - const verifiedSell = verificationDetails?.verifiedSellAmountCryptoBaseUnit; - const effectiveSellAmount = verifiedSell - ? (0, chain_adapters_1.bnOrZero)(verifiedSell).lt((0, chain_adapters_1.bnOrZero)(swap.sellAmountCryptoBaseUnit)) - ? verifiedSell - : swap.sellAmountCryptoBaseUnit - : swap.sellAmountCryptoBaseUnit; - let totalFeeUsd; - const feeAssetId = swap.affiliateFeeAssetId; - const feeAssetPrice = feeAssetId ? priceMap.get(feeAssetId) : null; - if (feeAssetId && feeAssetPrice) { - const sellAssetObj = swap.sellAsset; - const buyAssetObj = swap.buyAsset; - const feeAmountBaseUnit = this.estimateAffiliateFeeAmount(verifiedBps, swap.swapperName, effectiveSellAmount, swap.expectedBuyAmountCryptoBaseUnit); - const feeAssetPrecision = feeAssetId === sellAssetObj.assetId - ? sellAssetObj.precision - : buyAssetObj.precision; - const feeAmountHuman = parseFloat(feeAmountBaseUnit) / Math.pow(10, feeAssetPrecision); - totalFeeUsd = feeAmountHuman * feeAssetPrice; - } - else { - totalFeeUsd = (sellAmountUsd * verifiedBps) / 10000; - } - periodCommissionUsd += totalFeeUsd * commissionRate; - } - } - let allTimeCommissionUsd = 0; - for (const swap of allTimeSwaps) { - const sellAmountUsd = await this.resolveSwapUsdValue(swap, priceMap, freezeCutoff, calculateUsdValue); - if (sellAmountUsd === null) - continue; - const verificationDetails = swap.affiliateVerificationDetails; - const verifiedBps = verificationDetails?.affiliateBps; - if (verifiedBps && sellAmountUsd > 0) { - const commissionRate = this.getAffiliateCommissionRate(swap.origin, verifiedBps); - const verifiedSell = verificationDetails?.verifiedSellAmountCryptoBaseUnit; - const effectiveSellAmount = verifiedSell - ? (0, chain_adapters_1.bnOrZero)(verifiedSell).lt((0, chain_adapters_1.bnOrZero)(swap.sellAmountCryptoBaseUnit)) - ? verifiedSell - : swap.sellAmountCryptoBaseUnit - : swap.sellAmountCryptoBaseUnit; - let totalFeeUsd; - const feeAssetId = swap.affiliateFeeAssetId; - const feeAssetPrice = feeAssetId ? priceMap.get(feeAssetId) : null; - if (feeAssetId && feeAssetPrice) { - const sellAssetObj = swap.sellAsset; - const buyAssetObj = swap.buyAsset; - const feeAmountBaseUnit = this.estimateAffiliateFeeAmount(verifiedBps, swap.swapperName, effectiveSellAmount, swap.expectedBuyAmountCryptoBaseUnit); - const feeAssetPrecision = feeAssetId === sellAssetObj.assetId - ? sellAssetObj.precision - : buyAssetObj.precision; - const feeAmountHuman = parseFloat(feeAmountBaseUnit) / Math.pow(10, feeAssetPrecision); - totalFeeUsd = feeAmountHuman * feeAssetPrice; - } - else { - totalFeeUsd = (sellAmountUsd * verifiedBps) / 10000; - } - allTimeCommissionUsd += totalFeeUsd * commissionRate; - } - } - this.logger.log(`Affiliate fee calculation for ${affiliateAddress}: ` + - `Period: ${swapCount} swaps, $${totalSwapVolumeUsd.toFixed(2)} volume, $${periodCommissionUsd.toFixed(2)} commission | ` + - `All-time: ${allTimeSwaps.length} swaps, $${allTimeCommissionUsd.toFixed(2)} total commission`); - return { - affiliateAddress, - swapCount, - totalSwapVolumeUsd: totalSwapVolumeUsd.toFixed(2), - totalFeesCollectedUsd: allTimeCommissionUsd.toFixed(2), - referrerCommissionUsd: periodCommissionUsd.toFixed(2), - periodStart: startDate?.toISOString(), - periodEnd: endDate?.toISOString(), - }; - } - getAffiliateCommissionRate(origin, verifiedBps) { - if (origin === 'web') { - return SwapsService_1.WEB_REVENUE_SHARE; - } - if (!origin || verifiedBps <= SwapsService_1.API_BASE_BPS) - return 0; - return (verifiedBps - SwapsService_1.API_BASE_BPS) / verifiedBps; - } - estimateAffiliateFeeAmount(affiliateBps, swapperName, sellAmountCryptoBaseUnit, expectedBuyAmountCryptoBaseUnit) { - const strategy = (0, affiliateFeeAsset_1.getSwapperFeeStrategy)(swapperName); - const bpsMultiplier = affiliateBps / 10000; - switch (strategy) { - case 'sell_asset': - return (0, chain_adapters_1.bnOrZero)(sellAmountCryptoBaseUnit) - .times(bpsMultiplier) - .toFixed(0); - case 'buy_asset': - return (0, chain_adapters_1.bnOrZero)(expectedBuyAmountCryptoBaseUnit) - .times(bpsMultiplier) - .toFixed(0); - case 'fixed_base': - default: - return (0, chain_adapters_1.bnOrZero)(sellAmountCryptoBaseUnit) - .times(bpsMultiplier) - .toFixed(0); - } - } - async pollSwapStatus(swapId) { - try { - this.logger.log(`Polling status for swap: ${swapId}`); - const swap = await this.prisma.swap.findUnique({ - where: { swapId }, - }); - if (!swap) { - throw new Error(`Swap not found: ${swapId}`); - } - const sellAsset = swap.sellAsset; - const swapper = swapper_1.swappers[swap.swapperName]; - if (!swapper) { - throw new Error(`Swapper not found: ${swap.swapperName}`); - } - if (!swap.sellTxHash) { - throw new Error('Sell tx hash is required'); - } - const status = await swapper.checkTradeStatus({ - txHash: swap.sellTxHash ?? '', - chainId: sellAsset.chainId, - address: swap.sellAccountId, - swap: { - ...swap, - id: swap.swapId, - createdAt: swap.createdAt.getTime(), - updatedAt: swap.updatedAt.getTime(), - }, - stepIndex: 0, - config: { - VITE_UNCHAINED_THORCHAIN_HTTP_URL: process.env.VITE_UNCHAINED_THORCHAIN_HTTP_URL || '', - VITE_UNCHAINED_MAYACHAIN_HTTP_URL: process.env.VITE_UNCHAINED_MAYACHAIN_HTTP_URL || '', - VITE_UNCHAINED_COSMOS_HTTP_URL: process.env.VITE_UNCHAINED_COSMOS_HTTP_URL || '', - VITE_THORCHAIN_NODE_URL: process.env.VITE_THORCHAIN_NODE_URL || '', - VITE_MAYACHAIN_NODE_URL: process.env.VITE_MAYACHAIN_NODE_URL || '', - VITE_COWSWAP_BASE_URL: process.env.VITE_COWSWAP_BASE_URL || '', - VITE_CHAINFLIP_API_KEY: process.env.VITE_CHAINFLIP_API_KEY || '', - VITE_CHAINFLIP_API_URL: process.env.VITE_CHAINFLIP_API_URL || '', - VITE_JUPITER_API_URL: process.env.VITE_JUPITER_API_URL || '', - VITE_RELAY_API_URL: process.env.VITE_RELAY_API_URL || '', - VITE_PORTALS_BASE_URL: process.env.VITE_PORTALS_BASE_URL || '', - VITE_ZRX_BASE_URL: process.env.VITE_ZRX_BASE_URL || '', - VITE_THORCHAIN_MIDGARD_URL: process.env.VITE_THORCHAIN_MIDGARD_URL || '', - VITE_MAYACHAIN_MIDGARD_URL: process.env.VITE_MAYACHAIN_MIDGARD_URL || '', - VITE_UNCHAINED_BITCOIN_HTTP_URL: process.env.VITE_UNCHAINED_BITCOIN_HTTP_URL || '', - VITE_UNCHAINED_DOGECOIN_HTTP_URL: process.env.VITE_UNCHAINED_DOGECOIN_HTTP_URL || '', - VITE_UNCHAINED_LITECOIN_HTTP_URL: process.env.VITE_UNCHAINED_LITECOIN_HTTP_URL || '', - VITE_UNCHAINED_BITCOINCASH_HTTP_URL: process.env.VITE_UNCHAINED_BITCOINCASH_HTTP_URL || '', - VITE_UNCHAINED_ETHEREUM_HTTP_URL: process.env.VITE_UNCHAINED_ETHEREUM_HTTP_URL || '', - VITE_UNCHAINED_AVALANCHE_HTTP_URL: process.env.VITE_UNCHAINED_AVALANCHE_HTTP_URL || '', - VITE_UNCHAINED_BNBSMARTCHAIN_HTTP_URL: process.env.VITE_UNCHAINED_BNBSMARTCHAIN_HTTP_URL || '', - VITE_UNCHAINED_BASE_HTTP_URL: process.env.VITE_UNCHAINED_BASE_HTTP_URL || '', - VITE_NEAR_INTENTS_API_KEY: process.env.VITE_NEAR_INTENTS_API_KEY || '', - VITE_FEATURE_THORCHAINSWAP_LONGTAIL: true, - VITE_FEATURE_THORCHAINSWAP_L1_TO_LONGTAIL: true, - VITE_FEATURE_CHAINFLIP_SWAP_DCA: true, - }, - assertGetSolanaChainAdapter: (chainId) => { - return this.solanaChainAdapterService.assertGetSolanaChainAdapter(chainId); - }, - assertGetUtxoChainAdapter: (chainId) => { - return this.utxoChainAdapterService.assertGetUtxoChainAdapter(chainId); - }, - assertGetCosmosSdkChainAdapter: (chainId) => { - return this.cosmosSdkChainAdapterService.assertGetCosmosSdkChainAdapter(chainId); - }, - assertGetEvmChainAdapter: (chainId) => { - return this.evmChainAdapterService.assertGetEvmChainAdapter(chainId); - }, - fetchIsSmartContractAddressQuery: () => Promise.resolve(false), - }); - // Verify affiliate usage - let isAffiliateVerified; - let affiliateVerificationDetails; - try { - // Enrich metadata with swap fields needed for verification - const enrichedMetadata = { - ...swap.metadata, - receiveAddress: swap.receiveAddress, - expectedBuyAmountCryptoPrecision: swap.expectedBuyAmountCryptoPrecision, - createdAt: swap.createdAt.getTime(), - sellAssetPrecision: sellAsset.precision, - }; - const verificationResult = await this.swapVerificationService.verifySwapAffiliate(swapId, swap.swapperName, sellAsset.chainId, swap.sellTxHash || undefined, enrichedMetadata); - isAffiliateVerified = - verificationResult.isVerified && verificationResult.hasAffiliate; - if (verificationResult.isVerified) { - affiliateVerificationDetails = { - hasAffiliate: verificationResult.hasAffiliate, - affiliateBps: verificationResult.affiliateBps, - affiliateAddress: verificationResult.affiliateAddress, - verifiedSellAmountCryptoBaseUnit: verificationResult.verifiedSellAmountCryptoBaseUnit, - }; - } - this.logger.log(`Affiliate verification for swap ${swapId}: verified=${verificationResult.isVerified}, hasAffiliate=${verificationResult.hasAffiliate}`); - // Update the database with verification result - await this.prisma.swap.update({ - where: { swapId }, - data: { - isAffiliateVerified, - affiliateVerificationDetails: affiliateVerificationDetails || {}, - affiliateVerifiedAt: new Date(), - }, - }); - } - catch (verificationError) { - this.logger.warn(`Failed to verify affiliate for swap ${swapId}:`, verificationError); - // Don't fail the entire status check if verification fails - } - return { - status: status.status === unchained_client_1.TxStatus.Confirmed - ? 'SUCCESS' - : status.status === unchained_client_1.TxStatus.Failed - ? 'FAILED' - : 'PENDING', - sellTxHash: swap.sellTxHash, - buyTxHash: status.buyTxHash, - statusMessage: status.message, - isAffiliateVerified, - affiliateVerificationDetails, - }; - } - catch (error) { - this.logger.error(`Failed to poll swap status for ${swapId}:`, error); - return { - status: 'PENDING', - statusMessage: `Error polling status: ${error instanceof Error ? error.message : 'Unknown error'}`, - }; - } - } -}; -exports.SwapsService = SwapsService; -SwapsService.API_BASE_BPS = 10; -SwapsService.WEB_REVENUE_SHARE = 0.1; -exports.SwapsService = SwapsService = SwapsService_1 = __decorate([ - (0, common_1.Injectable)(), - __metadata("design:paramtypes", [prisma_service_1.PrismaService, - evm_service_1.EvmChainAdapterService, - utxo_service_1.UtxoChainAdapterService, - cosmos_sdk_service_1.CosmosSdkChainAdapterService, - solana_service_1.SolanaChainAdapterService, - swap_verification_service_1.SwapVerificationService]) -], SwapsService); diff --git a/apps/swap-service/src/utils/affiliateFeeAsset.js b/apps/swap-service/src/utils/affiliateFeeAsset.js deleted file mode 100644 index 83ede27..0000000 --- a/apps/swap-service/src/utils/affiliateFeeAsset.js +++ /dev/null @@ -1,57 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.resolveAffiliateFeeAssetId = resolveAffiliateFeeAssetId; -exports.getSwapperFeeStrategy = getSwapperFeeStrategy; -exports.resolveAffiliateFeeAsset = resolveAffiliateFeeAsset; -const SWAPPER_FEE_STRATEGY = { - THORChain: 'buy_asset', - MAYAChain: 'buy_asset', - 'CoW Swap': 'buy_asset', - '0x': 'buy_asset', - Jupiter: 'buy_asset', - Chainflip: 'buy_asset', - Across: 'buy_asset', - Portals: 'buy_asset', - Bebop: 'buy_asset', - ButterSwap: 'buy_asset', - 'STON.fi': 'buy_asset', - Cetus: 'buy_asset', - 'Sun.io': 'buy_asset', - 'NEAR Intents': 'buy_asset', - AVNU: 'sell_asset', - Relay: 'fixed_base', -}; -const BASE_USDC_ASSET_ID = 'eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'; -function resolveAffiliateFeeAssetId(swapperName, sellAsset, buyAsset) { - const strategy = SWAPPER_FEE_STRATEGY[swapperName]; - if (!strategy) - return null; - switch (strategy) { - case 'buy_asset': - return buyAsset.assetId; - case 'sell_asset': - return sellAsset.assetId; - case 'fixed_base': - return BASE_USDC_ASSET_ID; - default: - return null; - } -} -function getSwapperFeeStrategy(swapperName) { - return SWAPPER_FEE_STRATEGY[swapperName] ?? null; -} -function resolveAffiliateFeeAsset(swapperName, sellAsset, buyAsset) { - const strategy = SWAPPER_FEE_STRATEGY[swapperName]; - if (!strategy) - return null; - switch (strategy) { - case 'buy_asset': - return buyAsset; - case 'sell_asset': - return sellAsset; - case 'fixed_base': - return null; - default: - return null; - } -} diff --git a/apps/swap-service/src/utils/pricing.js b/apps/swap-service/src/utils/pricing.js deleted file mode 100644 index 4b40b8a..0000000 --- a/apps/swap-service/src/utils/pricing.js +++ /dev/null @@ -1,59 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.getAssetPriceUsd = getAssetPriceUsd; -exports.calculateUsdValue = calculateUsdValue; -const axios_1 = __importDefault(require("axios")); -const caip_1 = require("@shapeshiftoss/caip"); -// Simple in-memory cache -const priceCache = new Map(); -const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes -async function getAssetPriceUsd(asset) { - const cacheKey = asset.assetId; - // Check cache - const cached = priceCache.get(cacheKey); - if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) { - return cached.price; - } - try { - // Use CAIP adapters to dynamically get CoinGecko URL for any supported asset - const url = caip_1.adapters.makeCoingeckoAssetUrl(asset.assetId); - if (!url) { - console.warn(`No CoinGecko URL mapping for assetId: ${asset.assetId}`); - return null; - } - // Fetch price from CoinGecko - const { data } = await axios_1.default.get(url, { - timeout: 5000, - }); - const price = data?.market_data?.current_price?.usd || null; - if (price !== null) { - // Cache the result - priceCache.set(cacheKey, { price, timestamp: Date.now() }); - return price; - } - else { - console.warn(`No price data found for ${asset.assetId} (symbol: ${asset.symbol})`); - return null; - } - } - catch (error) { - console.error(`Failed to fetch price for ${asset.assetId}:`, error); - return null; - } -} -function calculateUsdValue(cryptoAmount, priceUsd) { - try { - const amount = parseFloat(cryptoAmount); - if (isNaN(amount)) - return '0'; - const usdValue = amount * priceUsd; - return usdValue.toFixed(2); - } - catch (error) { - console.error('Failed to calculate USD value:', error); - return '0'; - } -} diff --git a/apps/swap-service/src/verification/swap-verification.service.js b/apps/swap-service/src/verification/swap-verification.service.js deleted file mode 100644 index f394b19..0000000 --- a/apps/swap-service/src/verification/swap-verification.service.js +++ /dev/null @@ -1,859 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); -}; -var SwapVerificationService_1; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.SwapVerificationService = void 0; -const common_1 = require("@nestjs/common"); -const axios_1 = require("@nestjs/axios"); -const rxjs_1 = require("rxjs"); -const one_click_sdk_typescript_1 = require("@defuse-protocol/one-click-sdk-typescript"); -const swapper_1 = require("@shapeshiftoss/swapper"); -const THORCHAIN_PRECISION = 8; -const thorchainToNativePrecision = (thorchainAmount, nativePrecision) => { - const diff = nativePrecision - THORCHAIN_PRECISION; - if (diff === 0) - return thorchainAmount; - if (diff > 0) - return thorchainAmount + '0'.repeat(diff); - const trimmed = thorchainAmount.slice(0, diff); - return trimmed || '0'; -}; -let SwapVerificationService = SwapVerificationService_1 = class SwapVerificationService { - constructor(httpService) { - this.httpService = httpService; - this.logger = new common_1.Logger(SwapVerificationService_1.name); - this.oneClickServiceInitialized = false; - } - initializeOneClickService(apiKey) { - if (this.oneClickServiceInitialized) - return; - const oneClickBaseUrl = 'https://1click.chaindefuser.com'; - one_click_sdk_typescript_1.OpenAPI.BASE = oneClickBaseUrl; - one_click_sdk_typescript_1.OpenAPI.TOKEN = apiKey; - this.oneClickServiceInitialized = true; - this.logger.log('OneClickService initialized'); - } - async verifySwapAffiliate(swapId, protocol, sellChainId, txHash, metadata) { - try { - this.logger.log(`Verifying affiliate for swap ${swapId} on protocol ${protocol}`); - switch (protocol.toLowerCase()) { - case 'near': - case 'nearintents': - case 'near intents': - return await this.verifyNearIntents(swapId, metadata); - case 'relay': - return await this.verifyRelay(swapId, (metadata?.relayTransactionMetadata).relayId); - case 'cow swap': - return await this.verifyCowSwap(swapId, sellChainId, txHash, metadata); - case 'portals': - return await this.verifyPortals(swapId, sellChainId, metadata); - case 'thorchain': - return await this.verifyThorchain(swapId, txHash, metadata); - case 'maya': - case 'mayachain': - return await this.verifyMaya(swapId, txHash, metadata); - case 'chainflip': - return await this.verifyChainflip(swapId, metadata); - case '0x': - case 'zrx': - return await this.verifyZrx(swapId, txHash, metadata); - case 'bebop': - return await this.verifyBebop(swapId, txHash, metadata); - default: - return { - isVerified: false, - hasAffiliate: false, - protocol, - swapId, - error: `Verification not implemented for protocol: ${protocol}`, - }; - } - } - catch (error) { - this.logger.error(`Error verifying swap ${swapId} for protocol ${protocol}:`, error); - return { - isVerified: false, - hasAffiliate: false, - protocol, - swapId, - error: error instanceof Error ? error.message : 'Unknown error', - }; - } - } - async verifyNearIntents(swapId, metadata) { - // NEAR intents uses depositAddress to query execution status - // The depositAddress is stored in nearIntentsSpecific metadata - const depositAddress = metadata?.nearIntentsSpecific?.depositAddress; - if (!depositAddress) { - return { - isVerified: false, - hasAffiliate: false, - protocol: 'near', - swapId, - error: 'Missing depositAddress in metadata.nearIntentsSpecific', - }; - } - try { - // Initialize OneClickService with API key (same approach as web) - const apiKey = process.env.VITE_NEAR_INTENTS_API_KEY; - if (!apiKey) { - this.logger.error('Missing VITE_NEAR_INTENTS_API_KEY for NEAR Intents verification'); - return { - isVerified: false, - hasAffiliate: false, - protocol: 'near', - swapId, - error: 'Missing VITE_NEAR_INTENTS_API_KEY', - }; - } - this.initializeOneClickService(apiKey); - const statusResponse = await one_click_sdk_typescript_1.OneClickService.getExecutionStatus(depositAddress); - if (!statusResponse) { - return { - isVerified: false, - hasAffiliate: false, - protocol: 'near', - swapId, - error: 'No execution status found', - }; - } - // Check if the quote request contains affiliate fees - // SDK structure: statusResponse.quoteResponse.quoteRequest - const quoteRequest = statusResponse.quoteResponse?.quoteRequest; - // Verify it's ShapeShift's affiliate - // The referral field should be 'shapeshift' from the quote request - const referral = quoteRequest?.referral; - const shapeshiftReferral = process.env.SHAPESHIFT_NEAR_REFERRAL || 'shapeshift'; - const hasShapeshiftReferral = referral?.toLowerCase() === shapeshiftReferral.toLowerCase(); - // Check if there are app fees - const appFees = quoteRequest?.appFees || []; - const hasAppFees = appFees.length > 0; - const hasShapeshiftAffiliate = hasShapeshiftReferral && hasAppFees; - // Extract fee amount if present - let affiliateBps; - if (hasAppFees && appFees[0]) { - affiliateBps = appFees[0].fee; - } - const swapDetails = statusResponse.swapDetails; - const quoteAmounts = statusResponse.quoteResponse?.quote; - let verifiedSellAmountCryptoBaseUnit; - const rawDepositedAmount = swapDetails?.depositedAmount ?? - swapDetails?.amountIn ?? - quoteAmounts?.amountIn; - if (rawDepositedAmount) { - const sellAssetPrecision = metadata?.sellAssetPrecision; - if (sellAssetPrecision && rawDepositedAmount.includes('.')) { - const [whole, frac = ''] = rawDepositedAmount.split('.'); - verifiedSellAmountCryptoBaseUnit = - whole + - frac.padEnd(sellAssetPrecision, '0').slice(0, sellAssetPrecision); - } - else { - verifiedSellAmountCryptoBaseUnit = rawDepositedAmount; - } - } - return { - isVerified: true, - hasAffiliate: hasShapeshiftAffiliate, - affiliateBps, - affiliateAddress: hasShapeshiftAffiliate - ? shapeshiftReferral - : undefined, - verifiedSellAmountCryptoBaseUnit, - protocol: 'near', - swapId, - details: { - depositAddress, - referral, - appFees, - quoteRequest, - swapDetails, - }, - }; - } - catch (error) { - this.logger.error(`Error verifying NEAR intents for swap ${swapId}:`, error); - return { - isVerified: false, - hasAffiliate: false, - protocol: 'near', - swapId, - error: error instanceof Error - ? error.message - : 'Failed to fetch NEAR intents status', - }; - } - } - async verifyRelay(swapId, txHash) { - if (!txHash) { - return { - isVerified: false, - hasAffiliate: false, - protocol: 'relay', - swapId, - error: 'Missing txHash for Relay verification', - }; - } - try { - const relayApiUrl = process.env.VITE_RELAY_API_URL || 'https://api.relay.link'; - const requestUrl = `${relayApiUrl}/requests/v2?id=${txHash}`; - const response = await (0, rxjs_1.firstValueFrom)(this.httpService.get(requestUrl)); - const requests = response.data?.requests; - if (!requests || requests.length === 0) { - return { - isVerified: false, - hasAffiliate: false, - protocol: 'relay', - swapId, - error: 'No request data found from Relay API', - }; - } - const request = requests[0]; - // Check for referrer field at top level - const referrer = request.referrer; - const shapeshiftReferrer = process.env.SHAPESHIFT_RELAY_REFERRER || 'shapeshift'; - const hasShapeshiftReferrer = referrer?.toLowerCase() === shapeshiftReferrer.toLowerCase(); - // Check for appFees or paidAppFees in the data object - const appFees = request.data?.appFees || request.data?.paidAppFees || []; - // Extract affiliate info from appFees - let affiliateBps; - let affiliateAddress; - if (appFees.length > 0) { - // Get the first app fee entry (should be ShapeShift's) - const fee = appFees[0]; - affiliateBps = fee.bps ? parseInt(fee.bps) : undefined; - affiliateAddress = fee.recipient; - } - // Verification is successful if we have shapeshift as referrer AND we have app fees - const hasShapeshiftAffiliate = hasShapeshiftReferrer && appFees.length > 0; - const verifiedSellAmountCryptoBaseUnit = request.data?.inTxs?.[0]?.data?.value?.toString() ?? - request.data?.metadata?.currencyIn?.amount?.toString() ?? - undefined; - return { - isVerified: true, - hasAffiliate: hasShapeshiftAffiliate, - affiliateBps, - affiliateAddress, - verifiedSellAmountCryptoBaseUnit, - protocol: 'relay', - swapId, - details: { - txHash, - referrer, - appFees, - request, - }, - }; - } - catch (error) { - this.logger.error(`Error verifying Relay for swap ${swapId}:`, error); - return { - isVerified: false, - hasAffiliate: false, - protocol: 'relay', - swapId, - error: error instanceof Error - ? error.message - : 'Failed to fetch Relay request data', - }; - } - } - async verifyCowSwap(swapId, sellChainId, txHash, metadata) { - // SECURITY: Always verify appData from CowSwap API using appDataHash - // to prevent users from pushing fake data to abuse the referral system - const appDataHash = metadata?.cowswapQuoteSpecific?.quote?.appDataHash; - if (!appDataHash) { - this.logger.warn(`CowSwap - Missing appDataHash for swap ${swapId}`); - return { - isVerified: false, - hasAffiliate: false, - protocol: 'cowswap', - swapId, - error: 'Missing appDataHash in metadata', - }; - } - try { - // ALWAYS fetch appData from CowSwap API to verify it's legitimate - this.logger.log(`CowSwap - Fetching appData from API using hash ${appDataHash} for swap ${swapId}`); - const cowswapApiUrl = process.env.VITE_COWSWAP_BASE_URL || 'https://api.cow.fi'; - const cowNetwork = (0, swapper_1.assertGetCowNetwork)(sellChainId); - const response = await (0, rxjs_1.firstValueFrom)(this.httpService.get(`${cowswapApiUrl}/${cowNetwork}/api/v1/app_data/${appDataHash}`)); - const decodedAppData = JSON.parse(response.data.fullAppData); - // Check if appCode is "shapeshift" - const appCode = decodedAppData?.appCode; - const shapeshiftAppCode = process.env.SHAPESHIFT_COWSWAP_APPCODE || 'shapeshift'; - const hasShapeshiftAppCode = appCode?.toLowerCase() === shapeshiftAppCode.toLowerCase(); - // Extract partner fee information from metadata.partnerFee - const partnerFee = decodedAppData?.metadata?.partnerFee; - const affiliateBps = partnerFee?.bps; - const affiliateAddress = partnerFee?.recipient; - // We have ShapeShift affiliate if appCode is shapeshift AND we have partnerFee - const hasShapeshiftAffiliate = hasShapeshiftAppCode && !!partnerFee; - let verifiedSellAmountCryptoBaseUnit; - const orderUid = txHash || metadata?.cowswapOrderUid; - if (orderUid) { - try { - const orderResponse = await (0, rxjs_1.firstValueFrom)(this.httpService.get(`${cowswapApiUrl}/${cowNetwork}/api/v1/orders/${orderUid}`)); - verifiedSellAmountCryptoBaseUnit = - orderResponse.data?.executedSellAmountBeforeFees?.toString() ?? - orderResponse.data?.executedSellAmount?.toString(); - } - catch (orderErr) { - this.logger.warn(`CowSwap - Failed to fetch order ${orderUid} for amount verification:`, orderErr); - } - } - this.logger.log(`CowSwap verification for swap ${swapId}: appCode=${appCode}, hasPartnerFee=${!!partnerFee}, bps=${affiliateBps}, verified=${hasShapeshiftAffiliate}`); - return { - isVerified: true, - hasAffiliate: hasShapeshiftAffiliate, - affiliateBps: hasShapeshiftAffiliate && affiliateBps ? affiliateBps : undefined, - affiliateAddress: hasShapeshiftAffiliate ? affiliateAddress : undefined, - verifiedSellAmountCryptoBaseUnit, - protocol: 'cowswap', - swapId, - details: { - appCode, - partnerFee, - decodedAppData, - }, - }; - } - catch (error) { - this.logger.error(`Error verifying CowSwap for swap ${swapId}:`, error); - return { - isVerified: false, - hasAffiliate: false, - protocol: 'cowswap', - swapId, - error: error instanceof Error - ? error.message - : 'Failed to decode CowSwap appData', - }; - } - } - async verifyPortals(swapId, sellChainId, metadata) { - // SECURITY: Always verify partner address from Portals API using orderId - // to prevent users from pushing fake data to abuse the referral system - // Get the orderId from the swap (stored as the quote id) - const orderId = metadata?.portalsTransactionMetadata?.orderId; - if (!orderId) { - this.logger.warn(`Portals - Missing orderId for swap ${swapId}`); - return { - isVerified: false, - hasAffiliate: false, - protocol: 'portals', - swapId, - error: 'Missing orderId in metadata', - }; - } - // Get the expected treasury address for this chain - let expectedTreasuryAddress; - try { - expectedTreasuryAddress = (0, swapper_1.getTreasuryAddressFromChainId)(sellChainId); - } - catch { - this.logger.warn(`Portals - Unsupported chain for treasury address: ${sellChainId}`); - return { - isVerified: false, - hasAffiliate: false, - protocol: 'portals', - swapId, - error: `Unsupported chain for treasury address: ${sellChainId}`, - }; - } - try { - // ALWAYS fetch order status from Portals API to verify it's legitimate - this.logger.log(`Portals - Fetching order status from API using orderId ${orderId} for swap ${swapId}`); - const portalsProxyUrl = process.env.PORTALS_PROXY_URL || - 'https://api.proxy.shapeshift.com/api/v1/portals'; - const response = await (0, rxjs_1.firstValueFrom)(this.httpService.get(`${portalsProxyUrl}/v2/portal/status?orderId=${orderId}`)); - const orderData = response.data; - this.logger.log(`Portals - Fetched and verified order from API for swap ${swapId}`); - // Get partner from the API response context - const partner = orderData?.context?.partner; - if (!partner) { - this.logger.warn(`Portals - No partner found in API response for swap ${swapId}`); - return { - isVerified: false, - hasAffiliate: false, - protocol: 'portals', - swapId, - error: 'No partner found in Portals API response', - }; - } - // Verify partner matches the expected treasury address (case-insensitive for EVM addresses) - const hasShapeshiftAffiliate = partner.toLowerCase() === expectedTreasuryAddress.toLowerCase(); - // Extract fee information from the order context - // feeAmount and feeAmountUsd are in the context - const feeAmount = orderData?.context?.feeAmount; - const feeAmountUsd = orderData?.context?.feeAmountUsd; - const verifiedSellAmountCryptoBaseUnit = orderData?.context?.inputAmount?.toString() ?? undefined; - this.logger.log(`Portals verification for swap ${swapId}: partner=${partner}, expectedTreasury=${expectedTreasuryAddress}, verified=${hasShapeshiftAffiliate}, feeAmount=${feeAmount}`); - return { - isVerified: true, - hasAffiliate: hasShapeshiftAffiliate, - affiliateBps: metadata?.affiliateBps - ? parseInt(metadata.affiliateBps) - : undefined, - affiliateAddress: hasShapeshiftAffiliate - ? expectedTreasuryAddress - : undefined, - verifiedSellAmountCryptoBaseUnit, - protocol: 'portals', - swapId, - details: { - orderId, - partner, - expectedTreasuryAddress, - sellChainId, - feeAmount, - feeAmountUsd, - orderData, - }, - }; - } - catch (error) { - this.logger.error(`Error verifying Portals for swap ${swapId}:`, error); - return { - isVerified: false, - hasAffiliate: false, - protocol: 'portals', - swapId, - error: error instanceof Error - ? error.message - : 'Failed to verify Portals order', - }; - } - } - async verifyThorchain(swapId, txHash, metadata) { - if (!txHash) { - return { - isVerified: false, - hasAffiliate: false, - protocol: 'thorchain', - swapId, - error: 'Missing txHash for Thorchain verification', - }; - } - try { - // SECURITY: Query Thorchain node API to verify memo contains affiliate info - const nodeUrl = process.env.VITE_THORCHAIN_NODE_URL || - 'https://thornode.ninerealms.com'; - const txUrl = `${nodeUrl}/thorchain/tx/${txHash}`; - this.logger.log(`Thorchain - Fetching tx from node API: ${txUrl}`); - const response = await (0, rxjs_1.firstValueFrom)(this.httpService.get(txUrl)); - const observedTx = response.data?.observed_tx; - if (!observedTx || !observedTx.tx) { - return { - isVerified: false, - hasAffiliate: false, - protocol: 'thorchain', - swapId, - error: 'No observed transaction found', - }; - } - const memo = observedTx.tx.memo; - if (!memo) { - return { - isVerified: false, - hasAffiliate: false, - protocol: 'thorchain', - swapId, - error: 'No memo found in transaction', - }; - } - // Parse memo format: =:r:thor1dz68dtlzrxnjflha9vvs7yt7p77mqdnf5yugww:131082237:ss:0 - // The affiliate code is after the 4th colon, followed by fee in bps - const shapeshiftAffiliate = process.env.SHAPESHIFT_THORCHAIN_AFFILIATE || 'ss'; - const memoPattern = new RegExp(`:${shapeshiftAffiliate}:(\\d+)`, 'i'); - const memoMatch = memo.match(memoPattern); - const hasShapeshiftAffiliate = !!memoMatch; - const affiliateBps = memoMatch ? parseInt(memoMatch[1]) : undefined; - const coins = observedTx.tx.coins; - const sellAssetPrecision = metadata?.sellAssetPrecision ?? - THORCHAIN_PRECISION; - const firstCoinAmount = coins?.[0]?.amount; - const verifiedSellAmountCryptoBaseUnit = firstCoinAmount - ? thorchainToNativePrecision(firstCoinAmount, sellAssetPrecision) - : undefined; - this.logger.log(`Thorchain verification for swap ${swapId}: memo=${memo}, affiliate=${shapeshiftAffiliate}, hasAffiliate=${hasShapeshiftAffiliate}, bps=${affiliateBps}`); - return { - isVerified: true, - hasAffiliate: hasShapeshiftAffiliate, - affiliateBps: hasShapeshiftAffiliate && affiliateBps ? affiliateBps : undefined, - affiliateAddress: hasShapeshiftAffiliate - ? shapeshiftAffiliate - : undefined, - verifiedSellAmountCryptoBaseUnit, - protocol: 'thorchain', - swapId, - details: { - txHash, - memo, - observedTx, - }, - }; - } - catch (error) { - this.logger.error(`Error verifying Thorchain for swap ${swapId}:`, error); - return { - isVerified: false, - hasAffiliate: false, - protocol: 'thorchain', - swapId, - error: error instanceof Error - ? error.message - : 'Failed to fetch Thorchain data from node', - }; - } - } - async verifyMaya(swapId, txHash, metadata) { - if (!txHash) { - return { - isVerified: false, - hasAffiliate: false, - protocol: 'maya', - swapId, - error: 'Missing txHash for Maya verification', - }; - } - try { - // SECURITY: Query Maya node API to verify memo contains affiliate info - const nodeUrl = process.env.VITE_MAYACHAIN_NODE_URL || - 'https://mayanode.mayachain.info'; - const txUrl = `${nodeUrl}/mayachain/tx/${txHash}`; - this.logger.log(`Maya - Fetching tx from node API: ${txUrl}`); - const response = await (0, rxjs_1.firstValueFrom)(this.httpService.get(txUrl)); - const observedTx = response.data?.observed_tx; - if (!observedTx || !observedTx.tx) { - return { - isVerified: false, - hasAffiliate: false, - protocol: 'maya', - swapId, - error: 'No observed transaction found', - }; - } - const memo = observedTx.tx.memo; - if (!memo) { - return { - isVerified: false, - hasAffiliate: false, - protocol: 'maya', - swapId, - error: 'No memo found in transaction', - }; - } - // Parse memo format: =:r:maya1dz68dtlzrxnjflha9vvs7yt7p77mqdnf5yugww:131082237:ss:0 - // The affiliate code is after the 4th colon, followed by fee in bps - const shapeshiftAffiliate = process.env.SHAPESHIFT_MAYA_AFFILIATE || 'ssmaya'; - const memoPattern = new RegExp(`:${shapeshiftAffiliate}:(\\d+)`, 'i'); - const memoMatch = memo.match(memoPattern); - const hasShapeshiftAffiliate = !!memoMatch; - const affiliateBps = memoMatch ? parseInt(memoMatch[1]) : undefined; - const coins = observedTx.tx.coins; - const sellAssetPrecision = metadata?.sellAssetPrecision ?? - THORCHAIN_PRECISION; - const firstCoinAmount = coins?.[0]?.amount; - const verifiedSellAmountCryptoBaseUnit = firstCoinAmount - ? thorchainToNativePrecision(firstCoinAmount, sellAssetPrecision) - : undefined; - this.logger.log(`Maya verification for swap ${swapId}: memo=${memo}, affiliate=${shapeshiftAffiliate}, hasAffiliate=${hasShapeshiftAffiliate}, bps=${affiliateBps}`); - return { - isVerified: true, - hasAffiliate: hasShapeshiftAffiliate, - affiliateBps: hasShapeshiftAffiliate && affiliateBps ? affiliateBps : undefined, - affiliateAddress: hasShapeshiftAffiliate - ? shapeshiftAffiliate - : undefined, - verifiedSellAmountCryptoBaseUnit, - protocol: 'maya', - swapId, - details: { - txHash, - memo, - observedTx, - }, - }; - } - catch (error) { - this.logger.error(`Error verifying Maya for swap ${swapId}:`, error); - return { - isVerified: false, - hasAffiliate: false, - protocol: 'maya', - swapId, - error: error instanceof Error - ? error.message - : 'Failed to fetch Maya data from node', - }; - } - } - async verifyChainflip(swapId, metadata) { - const chainflipSwapId = metadata?.chainflipSwapId; - if (!chainflipSwapId) { - return { - isVerified: false, - hasAffiliate: false, - protocol: 'chainflip', - swapId, - error: 'Missing chainflipSwapId in metadata', - }; - } - try { - const chainflipApiUrl = process.env.VITE_CHAINFLIP_API_URL || 'https://api.chainflip.io'; - const statusUrl = `${chainflipApiUrl}/swaps/${chainflipSwapId}`; - const headers = {}; - const apiKey = process.env.VITE_CHAINFLIP_API_KEY; - if (apiKey) { - headers['Authorization'] = `Bearer ${apiKey}`; - } - const response = await (0, rxjs_1.firstValueFrom)(this.httpService.get(statusUrl, { headers })); - const swapData = response.data; - if (!swapData) { - return { - isVerified: false, - hasAffiliate: false, - protocol: 'chainflip', - swapId, - error: 'No swap data found from Chainflip API', - }; - } - // Check for affiliate information in the swap data - const affiliate = swapData.affiliate || swapData.affiliateName; - const affiliateBps = swapData.affiliateBps || swapData.affiliateFee; - const shapeshiftAffiliate = process.env.SHAPESHIFT_CHAINFLIP_AFFILIATE || 'shapeshift'; - const hasShapeshiftAffiliate = affiliate?.toLowerCase() === shapeshiftAffiliate.toLowerCase(); - const verifiedSellAmountCryptoBaseUnit = (swapData.depositAmount ?? - swapData.ingressAmount ?? - swapData.sourceAmount)?.toString(); - return { - isVerified: true, - hasAffiliate: hasShapeshiftAffiliate, - affiliateBps: hasShapeshiftAffiliate && affiliateBps - ? parseInt(String(affiliateBps)) - : undefined, - affiliateAddress: hasShapeshiftAffiliate - ? shapeshiftAffiliate - : undefined, - verifiedSellAmountCryptoBaseUnit, - protocol: 'chainflip', - swapId, - details: { - chainflipSwapId, - affiliate, - swapData, - }, - }; - } - catch (error) { - this.logger.error(`Error verifying Chainflip for swap ${swapId}:`, error); - return { - isVerified: false, - hasAffiliate: false, - protocol: 'chainflip', - swapId, - error: error instanceof Error - ? error.message - : 'Failed to fetch Chainflip swap data', - }; - } - } - async verifyZrx(swapId, txHash, metadata) { - const tradeHash = txHash || - metadata?.tradeHash || - metadata?.txHash; - if (!tradeHash) { - return { - isVerified: false, - hasAffiliate: false, - protocol: '0x', - swapId, - error: 'Missing tradeHash in metadata', - }; - } - try { - // Use 0x Trade Analytics API via ShapeShift proxy to verify the trade - const zrxProxyUrl = process.env.ZRX_PROXY_URL || - 'https://api.proxy.shapeshift.com/api/v1/zrx'; - const requestUrl = `${zrxProxyUrl}/trade-analytics/swap`; - const response = await (0, rxjs_1.firstValueFrom)(this.httpService.get(requestUrl)); - const trades = Array.isArray(response.data) - ? response.data - : response.data?.trades || response.data?.results || []; - const trade = trades.find((t) => t.txHash?.toLowerCase() === tradeHash.toLowerCase() || - t.transactionHash?.toLowerCase() === tradeHash.toLowerCase()); - if (!trade) { - return { - isVerified: false, - hasAffiliate: false, - protocol: '0x', - swapId, - error: `Trade not found in 0x analytics (searched ${trades.length} trades)`, - }; - } - // Check for ShapeShift's partner/integrator name - // The field could be integratorId, integratorName, or affiliateName - const integratorId = trade.integratorId || trade.integratorName || trade.affiliateName; - const shapeshiftIntegrator = process.env.SHAPESHIFT_0X_INTEGRATOR || 'ShapeShift'; - const hasShapeshiftAffiliate = integratorId?.toLowerCase() === shapeshiftIntegrator.toLowerCase(); - // Extract fee information - // The fee could be in integratorFee, affiliateFee, or partnerFee fields - // Note: 0x fees are typically in decimal format (e.g., 0.0015 for 15 bps) - const integratorFee = trade.integratorFee || trade.affiliateFee || trade.partnerFee; - let affiliateBps; - if (integratorFee) { - // Convert decimal fee to basis points (e.g., 0.0015 -> 15 bps) - affiliateBps = parseFloat(integratorFee) * 10000; - } - const verifiedSellAmountCryptoBaseUnit = (trade.sellAmount ?? - trade.inputTokenAmount ?? - trade.amount)?.toString(); - return { - isVerified: true, - hasAffiliate: hasShapeshiftAffiliate, - affiliateBps, - affiliateAddress: hasShapeshiftAffiliate - ? shapeshiftIntegrator - : undefined, - verifiedSellAmountCryptoBaseUnit, - protocol: '0x', - swapId, - details: { - tradeHash, - integratorId, - integratorFee, - trade, - }, - }; - } - catch (error) { - this.logger.error(`Error verifying 0x for swap ${swapId}:`, error); - return { - isVerified: false, - hasAffiliate: false, - protocol: '0x', - swapId, - error: error instanceof Error ? error.message : 'Failed to verify 0x trade', - }; - } - } - async verifyBebop(swapId, txHash, metadata) { - if (!txHash) { - return { - isVerified: false, - hasAffiliate: false, - protocol: 'bebop', - swapId, - error: 'Missing txHash for Bebop verification', - }; - } - try { - // Use trade history API to find the trade by source filter - const bebopApiUrl = process.env.VITE_BEBOP_API_URL || 'https://api.bebop.xyz'; - const shapeshiftSource = process.env.SHAPESHIFT_BEBOP_SOURCE || 'shapeshift'; - // Get swap timestamp to create time range (swap createdAt +/- 1 hour) - const swapTimestamp = metadata?.createdAt || Date.now(); - const oneHour = 60 * 60 * 1000; - const startNano = (swapTimestamp - oneHour) * 1000000; // Convert to nanoseconds - const endNano = (swapTimestamp + oneHour) * 1000000; - // Query trade history with source filter and time range - const queryParams = new URLSearchParams({ - start: startNano.toString(), - end: endNano.toString(), - source: shapeshiftSource, - }); - // Need source-auth header with API key to query by source - const apiKey = process.env.VITE_BEBOP_API_KEY; - if (!apiKey) { - this.logger.error('Missing VITE_BEBOP_API_KEY for Bebop verification'); - return { - isVerified: false, - hasAffiliate: false, - protocol: 'bebop', - swapId, - error: 'Missing VITE_BEBOP_API_KEY for source authentication', - }; - } - const headers = { - 'source-auth': apiKey, - }; - const requestUrl = `${bebopApiUrl}/history/v2/trades?${queryParams.toString()}`; - // Log request details - this.logger.log(`Bebop API Request - URL: ${requestUrl}`); - this.logger.log(`Bebop API Request - Params: ${JSON.stringify({ - start: startNano.toString(), - end: endNano.toString(), - source: shapeshiftSource, - swapTimestamp: new Date(swapTimestamp).toISOString(), - })}`); - this.logger.log(`Bebop API Request - Headers: { 'source-auth': '${apiKey.substring(0, 8)}...' }`); - this.logger.log(`Bebop API Request - Looking for txHash: ${txHash}`); - const response = await (0, rxjs_1.firstValueFrom)(this.httpService.get(requestUrl, { headers })); - this.logger.log(`Bebop API Response - Status: ${response.status}`); - this.logger.log(`Bebop API Response - Data: ${JSON.stringify(response.data)}`); - const trades = response.data?.results || []; - this.logger.log(`Bebop API Response - Found ${trades.length} trades`); - const trade = trades.find((t) => t.txHash?.toLowerCase() === txHash.toLowerCase()); - if (!trade) { - return { - isVerified: false, - hasAffiliate: false, - protocol: 'bebop', - swapId, - error: 'Trade not found in Bebop history', - }; - } - // Since we filtered by source=shapeshift, finding the trade means it was made through ShapeShift - const hasShapeshiftAffiliate = true; - // Extract partner fee from the response (partnerFeeBps is in basis points) - const partnerFeeBps = trade.partnerFeeBps; - const affiliateBps = partnerFeeBps != null ? Number(partnerFeeBps) : undefined; - const sellTokenEntries = trade.sellTokens - ? Object.values(trade.sellTokens) - : []; - const verifiedSellAmountCryptoBaseUnit = sellTokenEntries[0]?.amount?.toString() ?? undefined; - this.logger.log(`Bebop verification: trade found, partnerFeeBps=${partnerFeeBps}, hasAffiliate=true`); - return { - isVerified: true, - hasAffiliate: hasShapeshiftAffiliate, - affiliateBps, - affiliateAddress: shapeshiftSource, - verifiedSellAmountCryptoBaseUnit, - protocol: 'bebop', - swapId, - details: { - txHash, - trade, - partnerFeeBps, - partnerFeeNative: trade.partnerFeeNative, - }, - }; - } - catch (error) { - this.logger.error(`Error verifying Bebop for swap ${swapId}:`, error); - return { - isVerified: false, - hasAffiliate: false, - protocol: 'bebop', - swapId, - error: error instanceof Error - ? error.message - : 'Failed to verify Bebop trade', - }; - } - } -}; -exports.SwapVerificationService = SwapVerificationService; -exports.SwapVerificationService = SwapVerificationService = SwapVerificationService_1 = __decorate([ - (0, common_1.Injectable)(), - __metadata("design:paramtypes", [axios_1.HttpService]) -], SwapVerificationService); diff --git a/apps/swap-service/src/websocket/websocket.gateway.js b/apps/swap-service/src/websocket/websocket.gateway.js deleted file mode 100644 index 7a2bb90..0000000 --- a/apps/swap-service/src/websocket/websocket.gateway.js +++ /dev/null @@ -1,80 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); -}; -var __param = (this && this.__param) || function (paramIndex, decorator) { - return function (target, key) { decorator(target, key, paramIndex); } -}; -var WebsocketGateway_1; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.WebsocketGateway = void 0; -const websockets_1 = require("@nestjs/websockets"); -const socket_io_1 = require("socket.io"); -const common_1 = require("@nestjs/common"); -const swaps_service_1 = require("../swaps/swaps.service"); -let WebsocketGateway = WebsocketGateway_1 = class WebsocketGateway { - constructor(swapsService) { - this.swapsService = swapsService; - this.logger = new common_1.Logger(WebsocketGateway_1.name); - this.connectedClients = new Map(); - } - handleConnection(client) { - this.logger.log(`Client connected: ${client.id}`); - } - handleDisconnect(client) { - this.logger.log(`Client disconnected: ${client.id}`); - if (client.userId) { - this.connectedClients.delete(client.userId); - } - } - async handleGetSwaps(data, client) { - if (!client.userId) { - return { error: 'Not authenticated' }; - } - try { - const swaps = await this.swapsService.getSwapsByUser(client.userId, data.limit || 50); - return { success: true, swaps }; - } - catch (error) { - this.logger.error('Failed to get swaps', error); - return { error: 'Failed to get swaps' }; - } - } - sendSwapUpdateToUser(userId, swap) { - const client = this.connectedClients.get(userId); - if (client) { - client.emit('swapUpdate', swap); - } - this.server.to(`user:${userId}`).emit('swapUpdate', swap); - } - broadcastToAll(event, data) { - this.server.emit(event, data); - } -}; -exports.WebsocketGateway = WebsocketGateway; -__decorate([ - (0, websockets_1.WebSocketServer)(), - __metadata("design:type", socket_io_1.Server) -], WebsocketGateway.prototype, "server", void 0); -__decorate([ - (0, websockets_1.SubscribeMessage)('getSwaps'), - __param(0, (0, websockets_1.MessageBody)()), - __param(1, (0, websockets_1.ConnectedSocket)()), - __metadata("design:type", Function), - __metadata("design:paramtypes", [Object, Object]), - __metadata("design:returntype", Promise) -], WebsocketGateway.prototype, "handleGetSwaps", null); -exports.WebsocketGateway = WebsocketGateway = WebsocketGateway_1 = __decorate([ - (0, websockets_1.WebSocketGateway)({ - cors: { - origin: '*', - }, - }), - __metadata("design:paramtypes", [swaps_service_1.SwapsService]) -], WebsocketGateway); diff --git a/apps/user-service/src/app.module.js b/apps/user-service/src/app.module.js deleted file mode 100644 index 5956f4b..0000000 --- a/apps/user-service/src/app.module.js +++ /dev/null @@ -1,31 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.AppModule = void 0; -const common_1 = require("@nestjs/common"); -const config_1 = require("@nestjs/config"); -const users_controller_1 = require("./users/users.controller"); -const users_service_1 = require("./users/users.service"); -const referral_controller_1 = require("./referral/referral.controller"); -const referral_service_1 = require("./referral/referral.service"); -const prisma_service_1 = require("./prisma/prisma.service"); -let AppModule = class AppModule { -}; -exports.AppModule = AppModule; -exports.AppModule = AppModule = __decorate([ - (0, common_1.Module)({ - imports: [ - config_1.ConfigModule.forRoot({ - isGlobal: true, - envFilePath: '../../.env', - }), - ], - controllers: [users_controller_1.UsersController, referral_controller_1.ReferralController], - providers: [users_service_1.UsersService, referral_service_1.ReferralService, prisma_service_1.PrismaService], - }) -], AppModule); diff --git a/apps/user-service/src/main.js b/apps/user-service/src/main.js deleted file mode 100644 index f3ffdcc..0000000 --- a/apps/user-service/src/main.js +++ /dev/null @@ -1,22 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const core_1 = require("@nestjs/core"); -const app_module_1 = require("./app.module"); -async function bootstrap() { - const app = await core_1.NestFactory.create(app_module_1.AppModule); - // Enable CORS - app.enableCors({ - origin: process.env.ALLOWED_ORIGINS?.split(',') || [ - 'http://localhost:3000', - /\.shapeshift\.com$/, - ], - credentials: true, - }); - app.getHttpAdapter().get('/health', (_, res) => { - res.status(200).json({ status: 'ok' }); - }); - const port = process.env.PORT || 3000; - await app.listen(port); - console.log(`User service is running on: http://localhost:${port}`); -} -bootstrap(); diff --git a/apps/user-service/src/prisma/prisma.service.js b/apps/user-service/src/prisma/prisma.service.js deleted file mode 100644 index 2b91c98..0000000 --- a/apps/user-service/src/prisma/prisma.service.js +++ /dev/null @@ -1,23 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.PrismaService = void 0; -const common_1 = require("@nestjs/common"); -const client_1 = require("@prisma/client"); -let PrismaService = class PrismaService extends client_1.PrismaClient { - async onModuleInit() { - await this.$connect(); - } - async onModuleDestroy() { - await this.$disconnect(); - } -}; -exports.PrismaService = PrismaService; -exports.PrismaService = PrismaService = __decorate([ - (0, common_1.Injectable)() -], PrismaService); diff --git a/apps/user-service/src/referral/referral.controller.js b/apps/user-service/src/referral/referral.controller.js deleted file mode 100644 index 56a7dec..0000000 --- a/apps/user-service/src/referral/referral.controller.js +++ /dev/null @@ -1,135 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); -}; -var __param = (this && this.__param) || function (paramIndex, decorator) { - return function (target, key) { decorator(target, key, paramIndex); } -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.ReferralController = void 0; -const common_1 = require("@nestjs/common"); -const referral_service_1 = require("./referral.service"); -const shared_utils_1 = require("@shapeshift/shared-utils"); -let ReferralController = class ReferralController { - constructor(referralService) { - this.referralService = referralService; - } - async createReferralCode(data) { - if (!(0, shared_utils_1.isValidAccountId)(data.ownerAddress)) { - throw new Error('Invalid account ID'); - } - const hashedOwnerAddress = (0, shared_utils_1.hashAccountId)(data.ownerAddress); - const expiresAt = data.expiresAt ? new Date(data.expiresAt) : undefined; - return this.referralService.createReferralCode({ - code: data.code, - ownerAddress: hashedOwnerAddress, - maxUses: data.maxUses, - expiresAt, - }); - } - async useReferralCode(data) { - return this.referralService.useReferralCode(data); - } - async getAllReferralCodes(limit) { - return this.referralService.getAllReferralCodes(limit ? parseInt(limit) : 50); - } - async getReferralCodeByCode(code) { - return this.referralService.getReferralCodeByCode(code); - } - async getReferralCodesByOwner(ownerAddress) { - if (!(0, shared_utils_1.isValidAccountId)(ownerAddress)) { - throw new Error('Invalid account ID'); - } - const hashedOwnerAddress = (0, shared_utils_1.hashAccountId)(ownerAddress); - return this.referralService.getReferralCodesByOwner(hashedOwnerAddress); - } - async getReferralUsageByAddress(refereeAddress) { - return this.referralService.getReferralUsageByAddress(refereeAddress); - } - async deactivateReferralCode(code, data) { - if (!(0, shared_utils_1.isValidAccountId)(data.ownerAddress)) { - throw new Error('Invalid account ID'); - } - const hashedOwnerAddress = (0, shared_utils_1.hashAccountId)(data.ownerAddress); - return this.referralService.deactivateReferralCode(code, hashedOwnerAddress); - } - async getReferralStats(ownerAddress, startDate, endDate) { - if (!(0, shared_utils_1.isValidAccountId)(ownerAddress)) { - throw new Error('Invalid account ID'); - } - const hashedOwnerAddress = (0, shared_utils_1.hashAccountId)(ownerAddress); - const start = startDate ? new Date(startDate) : undefined; - const end = endDate ? new Date(endDate) : undefined; - return this.referralService.getReferralStatsByOwner(hashedOwnerAddress, start, end); - } -}; -exports.ReferralController = ReferralController; -__decorate([ - (0, common_1.Post)('codes'), - __param(0, (0, common_1.Body)()), - __metadata("design:type", Function), - __metadata("design:paramtypes", [Object]), - __metadata("design:returntype", Promise) -], ReferralController.prototype, "createReferralCode", null); -__decorate([ - (0, common_1.Post)('use'), - __param(0, (0, common_1.Body)()), - __metadata("design:type", Function), - __metadata("design:paramtypes", [Object]), - __metadata("design:returntype", Promise) -], ReferralController.prototype, "useReferralCode", null); -__decorate([ - (0, common_1.Get)('codes'), - __param(0, (0, common_1.Query)('limit')), - __metadata("design:type", Function), - __metadata("design:paramtypes", [String]), - __metadata("design:returntype", Promise) -], ReferralController.prototype, "getAllReferralCodes", null); -__decorate([ - (0, common_1.Get)('codes/:code'), - __param(0, (0, common_1.Param)('code')), - __metadata("design:type", Function), - __metadata("design:paramtypes", [String]), - __metadata("design:returntype", Promise) -], ReferralController.prototype, "getReferralCodeByCode", null); -__decorate([ - (0, common_1.Get)('owner/:ownerAddress'), - __param(0, (0, common_1.Param)('ownerAddress')), - __metadata("design:type", Function), - __metadata("design:paramtypes", [String]), - __metadata("design:returntype", Promise) -], ReferralController.prototype, "getReferralCodesByOwner", null); -__decorate([ - (0, common_1.Get)('usage/:refereeAddress'), - __param(0, (0, common_1.Param)('refereeAddress')), - __metadata("design:type", Function), - __metadata("design:paramtypes", [String]), - __metadata("design:returntype", Promise) -], ReferralController.prototype, "getReferralUsageByAddress", null); -__decorate([ - (0, common_1.Put)('codes/:code/deactivate'), - __param(0, (0, common_1.Param)('code')), - __param(1, (0, common_1.Body)()), - __metadata("design:type", Function), - __metadata("design:paramtypes", [String, Object]), - __metadata("design:returntype", Promise) -], ReferralController.prototype, "deactivateReferralCode", null); -__decorate([ - (0, common_1.Get)('stats/:ownerAddress'), - __param(0, (0, common_1.Param)('ownerAddress')), - __param(1, (0, common_1.Query)('startDate')), - __param(2, (0, common_1.Query)('endDate')), - __metadata("design:type", Function), - __metadata("design:paramtypes", [String, String, String]), - __metadata("design:returntype", Promise) -], ReferralController.prototype, "getReferralStats", null); -exports.ReferralController = ReferralController = __decorate([ - (0, common_1.Controller)('referrals'), - __metadata("design:paramtypes", [referral_service_1.ReferralService]) -], ReferralController); diff --git a/apps/user-service/src/referral/referral.service.js b/apps/user-service/src/referral/referral.service.js deleted file mode 100644 index 36ba984..0000000 --- a/apps/user-service/src/referral/referral.service.js +++ /dev/null @@ -1,244 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); -}; -var ReferralService_1; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.ReferralService = void 0; -const common_1 = require("@nestjs/common"); -const prisma_service_1 = require("../prisma/prisma.service"); -const shared_utils_1 = require("@shapeshift/shared-utils"); -let ReferralService = ReferralService_1 = class ReferralService { - constructor(prisma) { - this.prisma = prisma; - this.logger = new common_1.Logger(ReferralService_1.name); - this.swapServiceClient = new shared_utils_1.SwapServiceClient(); - } - async createReferralCode(data) { - try { - const existingCode = await this.prisma.referralCode.findUnique({ - where: { code: data.code }, - }); - if (existingCode) { - throw new common_1.BadRequestException('Referral code already exists'); - } - const referralCode = await this.prisma.referralCode.create({ - data: { - code: data.code, - ownerAddress: data.ownerAddress, - maxUses: data.maxUses, - expiresAt: data.expiresAt, - }, - }); - this.logger.log(`Created referral code: ${data.code} for address ${data.ownerAddress}`); - return referralCode; - } - catch (error) { - this.logger.error('Failed to create referral code', error); - throw error; - } - } - async useReferralCode(data) { - try { - const referralCode = await this.prisma.referralCode.findUnique({ - where: { code: data.code }, - include: { - _count: { - select: { usages: true }, - }, - }, - }); - if (!referralCode) { - throw new common_1.NotFoundException('Referral code not found'); - } - if (!referralCode.isActive) { - throw new common_1.BadRequestException('Referral code is inactive'); - } - if (referralCode.expiresAt && referralCode.expiresAt < new Date()) { - throw new common_1.BadRequestException('Referral code has expired'); - } - if (referralCode.maxUses && - referralCode._count.usages >= referralCode.maxUses) { - throw new common_1.BadRequestException('Referral code has reached maximum uses'); - } - if (referralCode.ownerAddress === data.refereeAddress) { - throw new common_1.BadRequestException('Cannot use your own referral code'); - } - const existingUsage = await this.prisma.referralUsage.findUnique({ - where: { refereeAddress: data.refereeAddress }, - }); - if (existingUsage) { - throw new common_1.BadRequestException('Address has already used a referral code'); - } - const usage = await this.prisma.referralUsage.create({ - data: { - referralCode: data.code, - refereeAddress: data.refereeAddress, - }, - }); - this.logger.log(`Referral code ${data.code} used by ${data.refereeAddress}`); - return usage; - } - catch (error) { - this.logger.error('Failed to use referral code', error); - throw error; - } - } - async getReferralCodeByCode(code) { - const referralCode = await this.prisma.referralCode.findUnique({ - where: { code }, - include: { - usages: { - where: { isActive: true }, - }, - _count: { - select: { usages: true }, - }, - }, - }); - return referralCode; - } - async getReferralCodesByOwner(ownerAddress) { - const referralCodes = await this.prisma.referralCode.findMany({ - where: { ownerAddress }, - include: { - usages: { - where: { isActive: true }, - }, - _count: { - select: { usages: true }, - }, - }, - orderBy: { createdAt: 'desc' }, - }); - return referralCodes; - } - async getReferralUsageByAddress(refereeAddress) { - const usage = await this.prisma.referralUsage.findUnique({ - where: { refereeAddress }, - }); - return usage; - } - async deactivateReferralCode(code, ownerAddress) { - try { - const referralCode = await this.prisma.referralCode.findUnique({ - where: { code }, - }); - if (!referralCode) { - throw new common_1.NotFoundException('Referral code not found'); - } - if (referralCode.ownerAddress !== ownerAddress) { - throw new common_1.BadRequestException('Not authorized to deactivate this referral code'); - } - const updatedCode = await this.prisma.referralCode.update({ - where: { code }, - data: { isActive: false }, - }); - this.logger.log(`Deactivated referral code: ${code}`); - return updatedCode; - } - catch (error) { - this.logger.error('Failed to deactivate referral code', error); - throw error; - } - } - async getAllReferralCodes(limit = 50) { - const referralCodes = await this.prisma.referralCode.findMany({ - take: limit, - include: { - usages: { - where: { isActive: true }, - }, - _count: { - select: { usages: true }, - }, - }, - orderBy: { createdAt: 'desc' }, - }); - return referralCodes; - } - async getReferralStatsByOwner(ownerAddress, startDate, endDate) { - const dateFilter = startDate && endDate - ? { - usedAt: { - gte: startDate, - lte: endDate, - }, - } - : {}; - const referralCodes = await this.prisma.referralCode.findMany({ - where: { ownerAddress }, - include: { - usages: { - where: { - isActive: true, - ...dateFilter, - }, - }, - _count: { - select: { usages: true }, - }, - }, - }); - const totalReferrals = referralCodes.reduce((sum, code) => sum + code.usages.length, 0); - const activeCodesCount = referralCodes.filter((code) => code.isActive).length; - // Fetch fee data from swap service for all codes - let totalFeesCollectedUsd = 0; - let totalReferrerCommissionUsd = 0; - const referralCodesWithFees = await Promise.all(referralCodes.map(async (code) => { - try { - const feeData = await this.swapServiceClient.calculateReferralFees(code.code, startDate, endDate); - const feesCollected = parseFloat(feeData.totalFeesCollectedUsd || '0'); - const referrerCommission = parseFloat(feeData.referrerCommissionUsd || '0'); - totalFeesCollectedUsd += feesCollected; - totalReferrerCommissionUsd += referrerCommission; - return { - code: code.code, - isActive: code.isActive, - createdAt: code.createdAt, - usageCount: code.usages.length, - maxUses: code.maxUses, - expiresAt: code.expiresAt, - swapCount: feeData.swapCount || 0, - swapVolumeUsd: feeData.totalSwapVolumeUsd || '0', - feesCollectedUsd: feeData.totalFeesCollectedUsd || '0', - referrerCommissionUsd: feeData.referrerCommissionUsd || '0', - }; - } - catch (error) { - this.logger.warn(`Failed to fetch fees for code ${code.code}:`, error); - return { - code: code.code, - isActive: code.isActive, - createdAt: code.createdAt, - usageCount: code.usages.length, - maxUses: code.maxUses, - expiresAt: code.expiresAt, - swapCount: 0, - swapVolumeUsd: '0', - feesCollectedUsd: '0', - referrerCommissionUsd: '0', - }; - } - })); - return { - totalReferrals, - activeCodesCount, - totalCodesCount: referralCodes.length, - totalFeesCollectedUsd: totalFeesCollectedUsd.toFixed(2), - totalReferrerCommissionUsd: totalReferrerCommissionUsd.toFixed(2), - referralCodes: referralCodesWithFees, - }; - } -}; -exports.ReferralService = ReferralService; -exports.ReferralService = ReferralService = ReferralService_1 = __decorate([ - (0, common_1.Injectable)(), - __metadata("design:paramtypes", [prisma_service_1.PrismaService]) -], ReferralService); diff --git a/apps/user-service/src/users/users.controller.js b/apps/user-service/src/users/users.controller.js deleted file mode 100644 index 5489433..0000000 --- a/apps/user-service/src/users/users.controller.js +++ /dev/null @@ -1,159 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); -}; -var __param = (this && this.__param) || function (paramIndex, decorator) { - return function (target, key) { decorator(target, key, paramIndex); } -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.UsersController = void 0; -const common_1 = require("@nestjs/common"); -const users_service_1 = require("./users.service"); -let UsersController = class UsersController { - constructor(usersService) { - this.usersService = usersService; - } - async createUser(data) { - return this.usersService.createUser(data); - } - async getOrCreateUser(data) { - return this.usersService.getOrCreateUserByAccountIds(data.accountIds, data.referralCode); - } - async getAllUsers(limit) { - return this.usersService.getAllUsers(limit ? parseInt(limit) : 50); - } - async getUserById(userId) { - return this.usersService.getUserById(userId); - } - async getUserByAccountId(accountId) { - return this.usersService.getUserByAccountId(accountId); - } - async userExistsWithAccountId(accountId) { - const exists = await this.usersService.userExistsWithAccountId(accountId); - return { exists }; - } - async getOrCreateUserByAccountId(data) { - return this.usersService.getOrCreateUserByAccountId(data.accountId); - } - async addAccountIds(userId, data) { - return this.usersService.addAccountIds(userId, data.accountIds); - } - async addAccountId(userId, data) { - return this.usersService.addAccountId({ - userId, - accountId: data.accountId, - }); - } - async registerDevice(userId, data) { - return this.usersService.registerDevice({ - userId, - deviceToken: data.deviceToken, - deviceType: data.deviceType, - }); - } - async getUserDevices(userId) { - return this.usersService.getUserDevices(userId); - } - async removeDevice(userId, deviceId) { - return this.usersService.removeDevice(userId, deviceId); - } -}; -exports.UsersController = UsersController; -__decorate([ - (0, common_1.Post)(), - __param(0, (0, common_1.Body)()), - __metadata("design:type", Function), - __metadata("design:paramtypes", [Object]), - __metadata("design:returntype", Promise) -], UsersController.prototype, "createUser", null); -__decorate([ - (0, common_1.Post)('get-or-create'), - __param(0, (0, common_1.Body)()), - __metadata("design:type", Function), - __metadata("design:paramtypes", [Object]), - __metadata("design:returntype", Promise) -], UsersController.prototype, "getOrCreateUser", null); -__decorate([ - (0, common_1.Get)(), - __param(0, (0, common_1.Query)('limit')), - __metadata("design:type", Function), - __metadata("design:paramtypes", [String]), - __metadata("design:returntype", Promise) -], UsersController.prototype, "getAllUsers", null); -__decorate([ - (0, common_1.Get)(':userId'), - __param(0, (0, common_1.Param)('userId')), - __metadata("design:type", Function), - __metadata("design:paramtypes", [String]), - __metadata("design:returntype", Promise) -], UsersController.prototype, "getUserById", null); -__decorate([ - (0, common_1.Get)('account/:accountId'), - __param(0, (0, common_1.Param)('accountId')), - __metadata("design:type", Function), - __metadata("design:paramtypes", [String]), - __metadata("design:returntype", Promise) -], UsersController.prototype, "getUserByAccountId", null); -__decorate([ - (0, common_1.Get)('exists/account/:accountId'), - __param(0, (0, common_1.Param)('accountId')), - __metadata("design:type", Function), - __metadata("design:paramtypes", [String]), - __metadata("design:returntype", Promise) -], UsersController.prototype, "userExistsWithAccountId", null); -__decorate([ - (0, common_1.Post)('get-or-create-by-account'), - __param(0, (0, common_1.Body)()), - __metadata("design:type", Function), - __metadata("design:paramtypes", [Object]), - __metadata("design:returntype", Promise) -], UsersController.prototype, "getOrCreateUserByAccountId", null); -__decorate([ - (0, common_1.Post)(':userId/account-ids'), - __param(0, (0, common_1.Param)('userId')), - __param(1, (0, common_1.Body)()), - __metadata("design:type", Function), - __metadata("design:paramtypes", [String, Object]), - __metadata("design:returntype", Promise) -], UsersController.prototype, "addAccountIds", null); -__decorate([ - (0, common_1.Post)(':userId/account-id'), - __param(0, (0, common_1.Param)('userId')), - __param(1, (0, common_1.Body)()), - __metadata("design:type", Function), - __metadata("design:paramtypes", [String, Object]), - __metadata("design:returntype", Promise) -], UsersController.prototype, "addAccountId", null); -__decorate([ - (0, common_1.Post)(':userId/devices'), - __param(0, (0, common_1.Param)('userId')), - __param(1, (0, common_1.Body)()), - __metadata("design:type", Function), - __metadata("design:paramtypes", [String, Object]), - __metadata("design:returntype", Promise) -], UsersController.prototype, "registerDevice", null); -__decorate([ - (0, common_1.Get)(':userId/devices'), - __param(0, (0, common_1.Param)('userId')), - __metadata("design:type", Function), - __metadata("design:paramtypes", [String]), - __metadata("design:returntype", Promise) -], UsersController.prototype, "getUserDevices", null); -__decorate([ - (0, common_1.Delete)(':userId/devices/:deviceId'), - __param(0, (0, common_1.Param)('userId')), - __param(1, (0, common_1.Param)('deviceId')), - __metadata("design:type", Function), - __metadata("design:paramtypes", [String, String]), - __metadata("design:returntype", Promise) -], UsersController.prototype, "removeDevice", null); -exports.UsersController = UsersController = __decorate([ - (0, common_1.Controller)('users'), - __metadata("design:paramtypes", [users_service_1.UsersService]) -], UsersController); diff --git a/apps/user-service/src/users/users.service.js b/apps/user-service/src/users/users.service.js deleted file mode 100644 index d643ff0..0000000 --- a/apps/user-service/src/users/users.service.js +++ /dev/null @@ -1,347 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); -}; -var UsersService_1; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.UsersService = void 0; -const common_1 = require("@nestjs/common"); -const prisma_service_1 = require("../prisma/prisma.service"); -const referral_service_1 = require("../referral/referral.service"); -const shared_utils_1 = require("@shapeshift/shared-utils"); -let UsersService = UsersService_1 = class UsersService { - constructor(prisma, referralService) { - this.prisma = prisma; - this.referralService = referralService; - this.logger = new common_1.Logger(UsersService_1.name); - } - // Type assertion helpers for Prisma results - assertDeviceType(deviceType) { - if (deviceType === 'MOBILE' || deviceType === 'WEB') { - return deviceType; - } - throw new Error(`Invalid device type: ${deviceType}`); - } - async findUserByHashedAccountId(hashedAccountId) { - const user = await this.prisma.user.findFirst({ - where: { - userAccounts: { - some: { - accountId: hashedAccountId, - }, - }, - }, - include: { - userAccounts: true, - devices: true, - }, - }); - return user; - } - async createUser(data) { - try { - const hashedAccountIds = data.accountIds.map((id) => (0, shared_utils_1.hashAccountId)(id)); - for (const hashedAccountId of hashedAccountIds) { - const existingUser = await this.findUserByHashedAccountId(hashedAccountId); - if (existingUser) { - this.logger.log(`User already exists with account ID: ${existingUser.id}`); - const newAccountIds = hashedAccountIds.filter((id) => !existingUser.userAccounts.some((ua) => ua.accountId === id)); - if (newAccountIds.length > 0) { - await this.addHashedAccountIds(existingUser.id, newAccountIds); - } - return this.getUserById(existingUser.id); - } - } - const user = await this.prisma.user.create({ - data: { - userAccounts: { - create: hashedAccountIds.map((id) => ({ - accountId: id, - })), - }, - }, - }); - this.logger.log(`Created new user: ${user.id} with ${hashedAccountIds.length} account IDs`); - return this.getUserById(user.id); - } - catch (error) { - this.logger.error('Failed to create user', error); - throw error; - } - } - async addAccountIds(userId, accountIds) { - try { - const hashedAccountIds = accountIds.map((id) => { - if (!(0, shared_utils_1.isValidAccountId)(id)) { - throw new Error('Invalid account ID'); - } - return (0, shared_utils_1.hashAccountId)(id); - }); - return this.addHashedAccountIds(userId, hashedAccountIds); - } - catch (error) { - this.logger.error('Failed to add account IDs', error); - throw error; - } - } - async addHashedAccountIds(userId, hashedAccountIds) { - try { - const userAccounts = await Promise.all(hashedAccountIds.map((hashedAccountId) => this.prisma.userAccount.upsert({ - where: { - userId_accountId: { - userId, - accountId: hashedAccountId, - }, - }, - update: {}, - create: { - userId, - accountId: hashedAccountId, - }, - }))); - this.logger.log(`Added ${userAccounts.length} account IDs for user ${userId}`); - return userAccounts; - } - catch (error) { - this.logger.error('Failed to add hashed account IDs', error); - throw error; - } - } - async addAccountId(data) { - try { - if (!(0, shared_utils_1.isValidAccountId)(data.accountId)) { - throw new Error('Invalid account ID'); - } - const hashedAccountId = (0, shared_utils_1.hashAccountId)(data.accountId); - const existingUser = await this.findUserByHashedAccountId(hashedAccountId); - if (existingUser && existingUser.id !== data.userId) { - throw new Error(`Account ID already belongs to user ${existingUser.id}`); - } - const userAccount = await this.prisma.userAccount.upsert({ - where: { - userId_accountId: { - userId: data.userId, - accountId: hashedAccountId, - }, - }, - update: {}, - create: { - userId: data.userId, - accountId: hashedAccountId, - }, - }); - this.logger.log(`Added account ID for user ${data.userId}`); - return userAccount; - } - catch (error) { - this.logger.error('Failed to add account ID', error); - throw error; - } - } - async getUserById(userId) { - const user = await this.prisma.user.findUnique({ - where: { id: userId }, - include: { - userAccounts: true, - devices: true, - }, - }); - return user; - } - async getUserByAccountId(accountId) { - if (!(0, shared_utils_1.isValidAccountId)(accountId)) { - throw new Error('Invalid account ID'); - } - const hashedAccountId = (0, shared_utils_1.hashAccountId)(accountId); - const user = await this.prisma.user.findFirst({ - where: { - userAccounts: { - some: { - accountId: hashedAccountId, - }, - }, - }, - include: { - userAccounts: true, - devices: true, - }, - }); - return user; - } - async getAllUsers(limit = 50) { - const users = await this.prisma.user.findMany({ - take: limit, - include: { - userAccounts: true, - devices: { - where: { isActive: true }, - }, - }, - orderBy: { createdAt: 'desc' }, - }); - return users; - } - async userExistsWithAccountId(accountId) { - if (!(0, shared_utils_1.isValidAccountId)(accountId)) { - return false; - } - const hashedAccountId = (0, shared_utils_1.hashAccountId)(accountId); - const user = await this.findUserByHashedAccountId(hashedAccountId); - return !!user; - } - async getOrCreateUserByAccountId(accountId) { - if (!(0, shared_utils_1.isValidAccountId)(accountId)) { - throw new Error('Invalid account ID'); - } - const hashedAccountId = (0, shared_utils_1.hashAccountId)(accountId); - const existingUser = await this.findUserByHashedAccountId(hashedAccountId); - if (existingUser) { - this.logger.log(`Found existing user: ${existingUser.id} for account ID`); - return existingUser; - } - return this.createUser({ - accountIds: [accountId], - }); - } - async getOrCreateUserByAccountIds(accountIds, referralCode) { - this.logger.log(`getOrCreateUserByAccountIds called with accountIds: ${JSON.stringify(accountIds)}, referralCode: ${referralCode}`); - if (!accountIds || accountIds.length === 0) { - throw new Error('At least one account ID is required'); - } - // Validate all account IDs - accountIds.forEach((id) => { - if (!(0, shared_utils_1.isValidAccountId)(id)) { - throw new Error(`Invalid account ID: ${id}`); - } - }); - const hashedAccountIds = accountIds.map((id) => (0, shared_utils_1.hashAccountId)(id)); - this.logger.log(`Hashed account IDs: ${JSON.stringify(hashedAccountIds)}`); - for (const hashedAccountId of hashedAccountIds) { - const existingUser = await this.findUserByHashedAccountId(hashedAccountId); - if (existingUser) { - this.logger.log(`Found existing user: ${existingUser.id} for account ID`); - const newAccountIds = hashedAccountIds.filter((id) => !existingUser.userAccounts.some((ua) => ua.accountId === id)); - if (newAccountIds.length > 0) { - await this.addHashedAccountIds(existingUser.id, newAccountIds); - } - // Try to apply referral code for existing user if provided - if (referralCode) { - try { - // Use the first hashed account ID as referee address for privacy - await this.referralService.useReferralCode({ - code: referralCode, - refereeAddress: hashedAccountIds[0], - }); - this.logger.log(`Successfully applied referral code ${referralCode} for existing user ${existingUser.id}`); - } - catch (error) { - // Log but don't fail - user might already be referred or code might be invalid - this.logger.log(`Could not apply referral code ${referralCode} for existing user ${existingUser.id}: ${error instanceof Error ? error.message : String(error)}`); - } - } - const result = await this.getUserById(existingUser.id); - this.logger.log(`Returning existing user: ${result?.id}`); - return result; - } - } - this.logger.log(`No existing user found, creating new user with ${hashedAccountIds.length} account IDs`); - const user = await this.prisma.user.create({ - data: { - userAccounts: { - create: hashedAccountIds.map((id) => ({ - accountId: id, - })), - }, - }, - }); - this.logger.log(`Created new user: ${user.id} with ${hashedAccountIds.length} account IDs`); - // Handle referral code if provided - if (referralCode) { - try { - // Use the first hashed account ID as referee address for privacy - await this.referralService.useReferralCode({ - code: referralCode, - refereeAddress: hashedAccountIds[0], - }); - this.logger.log(`Successfully applied referral code ${referralCode} for new user ${user.id}`); - } - catch (error) { - this.logger.warn(`Failed to apply referral code ${referralCode} for user ${user.id}:`, error); - // Don't fail user creation if referral code application fails - } - } - const result = await this.getUserById(user.id); - this.logger.log(`Returning new user: ${result?.id}`); - return result; - } - async registerDevice(data) { - try { - // Check if user exists - const user = await this.getUserById(data.userId); - if (!user) { - throw new Error('User not found'); - } - const device = await this.prisma.device.upsert({ - where: { - deviceToken: data.deviceToken, - }, - update: { - userId: data.userId, - deviceType: data.deviceType, - isActive: true, - }, - create: { - deviceToken: data.deviceToken, - deviceType: data.deviceType, - userId: data.userId, - }, - }); - this.logger.log(`Device registered: ${data.deviceToken} for user ${data.userId} (${data.deviceType})`); - return device; - } - catch (error) { - this.logger.error('Failed to register device', error); - throw error; - } - } - async getUserDevices(userId) { - const devices = await this.prisma.device.findMany({ - where: { - userId, - isActive: true, - }, - }); - return devices; - } - async removeDevice(userId, deviceId) { - try { - await this.prisma.device.updateMany({ - where: { - id: deviceId, - userId, - }, - data: { - isActive: false, - }, - }); - this.logger.log(`Removed device ${deviceId} for user ${userId}`); - return { success: true }; - } - catch (error) { - this.logger.error('Failed to remove device', error); - throw error; - } - } -}; -exports.UsersService = UsersService; -exports.UsersService = UsersService = UsersService_1 = __decorate([ - (0, common_1.Injectable)(), - __metadata("design:paramtypes", [prisma_service_1.PrismaService, - referral_service_1.ReferralService]) -], UsersService); diff --git a/packages/shared-types/src/index.js b/packages/shared-types/src/index.js deleted file mode 100644 index c8ad2e5..0000000 --- a/packages/shared-types/src/index.js +++ /dev/null @@ -1,2 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/packages/shared-utils/src/index.js b/packages/shared-utils/src/index.js deleted file mode 100644 index aa47be4..0000000 --- a/packages/shared-utils/src/index.js +++ /dev/null @@ -1,115 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -Object.defineProperty(exports, "__esModule", { value: true }); -exports.SwapServiceClient = exports.NotificationsServiceClient = exports.UserServiceClient = exports.getOptionalEnvVar = exports.getRequiredEnvVar = exports.createPaginatedResponse = exports.createError = exports.parseDate = exports.formatDate = exports.isValidAccountId = exports.hashAccountId = void 0; -const crypto = __importStar(require("crypto")); -const caip_1 = require("@shapeshiftoss/caip"); -// Hash utilities -const hashAccountId = (accountId, salt) => { - const defaultSalt = process.env.ACCOUNT_ID_SALT || 'default-salt-change-in-production'; - const saltToUse = salt || defaultSalt; - return crypto - .createHash('sha256') - .update(accountId + saltToUse) - .digest('hex'); -}; -exports.hashAccountId = hashAccountId; -// Validation utilities -const isValidAccountId = (accountId) => { - try { - // Try to parse using the library - this works for supported chains - (0, caip_1.fromAccountId)(accountId); - return true; - } - catch { - // If library parsing fails, check if it at least matches CAIP-10 format - // Format: chainNamespace:chainReference:accountAddress - // Examples: - // - EVM: eip155:1:0x1234567890abcdef1234567890abcdef12345678 - // - Tron: tron:0x2b6653dc:TUrqPcjwrdz4u2tgSsrUoPHYYLe32u4Zea - // - Bitcoin: bip122:000000000019d6689c085ae165831e93:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa - // - Solana: solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK - // - Cosmos: cosmos:cosmoshub-4:cosmos1..., cosmos:mayachain-mainnet:maya1... - // More permissive CAIP-10 regex that handles various address formats - const caip10Regex = /^[a-z0-9-]+:[a-z0-9-]+:[a-zA-Z0-9]+$/; - return caip10Regex.test(accountId); - } -}; -exports.isValidAccountId = isValidAccountId; -// Date utilities -const formatDate = (date) => { - return date.toISOString(); -}; -exports.formatDate = formatDate; -const parseDate = (dateString) => { - return new Date(dateString); -}; -exports.parseDate = parseDate; -const createError = (message, code, details) => { - const error = new Error(message); - error.code = code; - error.details = details; - return error; -}; -exports.createError = createError; -// Pagination utilities -const createPaginatedResponse = (data, total, page, limit) => { - return { - data, - total, - page, - limit, - hasMore: page * limit < total, - }; -}; -exports.createPaginatedResponse = createPaginatedResponse; -// Environment utilities -const getRequiredEnvVar = (name) => { - const value = process.env[name]; - if (!value) { - throw new Error(`Required environment variable ${name} is not set`); - } - return value; -}; -exports.getRequiredEnvVar = getRequiredEnvVar; -const getOptionalEnvVar = (name, defaultValue) => { - return process.env[name] || defaultValue; -}; -exports.getOptionalEnvVar = getOptionalEnvVar; -// Service clients -var service_clients_1 = require("./service-clients"); -Object.defineProperty(exports, "UserServiceClient", { enumerable: true, get: function () { return service_clients_1.UserServiceClient; } }); -Object.defineProperty(exports, "NotificationsServiceClient", { enumerable: true, get: function () { return service_clients_1.NotificationsServiceClient; } }); -Object.defineProperty(exports, "SwapServiceClient", { enumerable: true, get: function () { return service_clients_1.SwapServiceClient; } }); diff --git a/packages/shared-utils/src/service-clients.js b/packages/shared-utils/src/service-clients.js deleted file mode 100644 index 35bae2f..0000000 --- a/packages/shared-utils/src/service-clients.js +++ /dev/null @@ -1,106 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.SwapServiceClient = exports.NotificationsServiceClient = exports.UserServiceClient = void 0; -const axios_1 = __importDefault(require("axios")); -const index_1 = require("./index"); -class UserServiceClient { - constructor() { - const baseUrl = (0, index_1.getRequiredEnvVar)('USER_SERVICE_URL'); - this.axios = axios_1.default.create({ - baseURL: baseUrl, - headers: { - 'Content-Type': 'application/json', - }, - }); - } - async getUserById(userId) { - const response = await this.axios.get(`/users/${userId}`); - return response.data; - } - async getUserByAccountId(accountId) { - const response = await this.axios.get(`/users/account/${accountId}`); - return response.data; - } - async getOrCreateUserByAccountIds(accountIds) { - const response = await this.axios.post('/users/get-or-create', { - accountIds, - }); - return response.data; - } - async getUserDevices(userId) { - const response = await this.axios.get(`/users/${userId}/devices`); - return response.data; - } - async getUserReferralCode(userId) { - try { - const user = await this.getUserById(userId); - if (!user || !user.userAccounts || user.userAccounts.length === 0) { - return null; - } - // Get the first account's hashed ID to check referral usage - const hashedAccountId = user.userAccounts[0].accountId; - const response = await this.axios.get(`/referrals/usage/${hashedAccountId}`); - return response.data?.referralCode || null; - } - catch { - // If no referral usage found, return null - return null; - } - } - async getReferralUsages(referralCode) { - try { - const response = await this.axios.get(`/referrals/codes/${referralCode}`); - return response.data?.usages || []; - } - catch { - // If code not found or no usages, return empty array - return []; - } - } -} -exports.UserServiceClient = UserServiceClient; -class NotificationsServiceClient { - constructor() { - const baseUrl = (0, index_1.getRequiredEnvVar)('NOTIFICATIONS_SERVICE_URL'); - this.axios = axios_1.default.create({ - baseURL: baseUrl, - headers: { - 'Content-Type': 'application/json', - }, - }); - } - async createNotification(data) { - const response = await this.axios.post('/notifications', data); - return response.data; - } - async sendNotificationToUser(data) { - const response = await this.axios.post('/notifications/send-to-user', data); - return response.data; - } -} -exports.NotificationsServiceClient = NotificationsServiceClient; -class SwapServiceClient { - constructor() { - const baseUrl = (0, index_1.getRequiredEnvVar)('SWAP_SERVICE_URL'); - this.axios = axios_1.default.create({ - baseURL: baseUrl, - headers: { - 'Content-Type': 'application/json', - }, - }); - } - async calculateReferralFees(referralCode, startDate, endDate) { - const params = new URLSearchParams(); - if (startDate) - params.append('startDate', startDate.toISOString()); - if (endDate) - params.append('endDate', endDate.toISOString()); - const url = `/swaps/referral-fees/${referralCode}${params.toString() ? `?${params.toString()}` : ''}`; - const response = await this.axios.get(url); - return response.data; - } -} -exports.SwapServiceClient = SwapServiceClient; From e9a9e7164e11c7042de41ee032d448d286a078d3 Mon Sep 17 00:00:00 2001 From: NeOMakinG <14963751+NeOMakinG@users.noreply.github.com> Date: Wed, 25 Feb 2026 16:48:34 +0100 Subject: [PATCH 5/6] feat: hnnnng --- .eslintcache | 1 + apps/notifications-service/prisma.config.js | 6 - apps/notifications-service/src/app.module.ts | 2 +- apps/notifications-service/src/main.ts | 5 +- apps/swap-service/src/app.module.ts | 12 +- .../src/lib/chain-adapter-init.service.ts | 20 + .../src/lib/chain-adapters/evm.service.ts | 298 +++- .../src/lib/chain-adapters/near.service.ts | 55 + .../lib/chain-adapters/starknet.service.ts | 48 + .../src/lib/chain-adapters/sui.service.ts | 48 + .../src/lib/chain-adapters/ton.service.ts | 48 + .../src/lib/chain-adapters/tron.service.ts | 54 + .../src/lib/chain-adapters/utxo.service.ts | 26 +- apps/swap-service/src/main.ts | 6 +- .../src/swaps/swaps.controller.ts | 26 +- apps/swap-service/src/swaps/swaps.service.ts | 72 +- .../verification/swap-verification.service.ts | 564 ++++++ apps/user-service/prisma.config.js | 6 - apps/user-service/src/app.module.ts | 2 +- apps/user-service/src/main.ts | 4 +- package.json | 10 +- tests/TESTING.md | 396 ++++ tests/test-all-swappers.mjs | 776 ++++++++ yarn.lock | 1587 ++++++++++++++--- 24 files changed, 3785 insertions(+), 287 deletions(-) create mode 100644 .eslintcache delete mode 100644 apps/notifications-service/prisma.config.js create mode 100644 apps/swap-service/src/lib/chain-adapters/near.service.ts create mode 100644 apps/swap-service/src/lib/chain-adapters/starknet.service.ts create mode 100644 apps/swap-service/src/lib/chain-adapters/sui.service.ts create mode 100644 apps/swap-service/src/lib/chain-adapters/ton.service.ts create mode 100644 apps/swap-service/src/lib/chain-adapters/tron.service.ts delete mode 100644 apps/user-service/prisma.config.js create mode 100644 tests/TESTING.md create mode 100644 tests/test-all-swappers.mjs diff --git a/.eslintcache b/.eslintcache new file mode 100644 index 0000000..80e246c --- /dev/null +++ b/.eslintcache @@ -0,0 +1 @@ +[{"/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/prisma.config.ts":"1","/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/src/app.module.ts":"2","/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/src/main.ts":"3","/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/src/notifications/notifications.controller.ts":"4","/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/src/notifications/notifications.service.ts":"5","/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/src/prisma/prisma.service.ts":"6","/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/src/websocket/websocket.gateway.ts":"7","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/prisma.config.ts":"8","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/app.module.ts":"9","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapter-init.service.ts":"10","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapter-manager.service.ts":"11","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/cosmos-sdk.service.ts":"12","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/evm.service.ts":"13","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/solana.service.ts":"14","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/utxo.service.ts":"15","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/main.ts":"16","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/polling/swap-polling.service.ts":"17","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/prisma/prisma.service.ts":"18","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/swaps/swaps.controller.ts":"19","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/swaps/swaps.service.ts":"20","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/utils/affiliateFeeAsset.ts":"21","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/utils/pricing.ts":"22","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/verification/swap-verification.service.ts":"23","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/websocket/websocket.gateway.ts":"24","/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/prisma.config.ts":"25","/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/app.module.ts":"26","/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/main.ts":"27","/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/prisma/prisma.service.ts":"28","/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/referral/referral.controller.ts":"29","/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/referral/referral.service.ts":"30","/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/users/users.controller.ts":"31","/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/users/users.service.ts":"32","/Users/0xm4king/Projects/shapeshift-backend/packages/shared-types/dist/index.d.ts":"33","/Users/0xm4king/Projects/shapeshift-backend/packages/shared-types/src/index.ts":"34","/Users/0xm4king/Projects/shapeshift-backend/packages/shared-utils/dist/index.d.ts":"35","/Users/0xm4king/Projects/shapeshift-backend/packages/shared-utils/dist/service-clients.d.ts":"36","/Users/0xm4king/Projects/shapeshift-backend/packages/shared-utils/src/index.ts":"37","/Users/0xm4king/Projects/shapeshift-backend/packages/shared-utils/src/service-clients.ts":"38","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/near.service.ts":"39","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/starknet.service.ts":"40","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/sui.service.ts":"41","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/ton.service.ts":"42","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/tron.service.ts":"43"},{"size":147,"mtime":1765450973236,"results":"44","hashOfConfig":"45"},{"size":703,"mtime":1771956054663,"results":"46","hashOfConfig":"45"},{"size":726,"mtime":1771951821026,"results":"47","hashOfConfig":"45"},{"size":2013,"mtime":1771935244697,"results":"48","hashOfConfig":"45"},{"size":7691,"mtime":1771935481678,"results":"49","hashOfConfig":"45"},{"size":354,"mtime":1771934886563,"results":"50","hashOfConfig":"45"},{"size":2208,"mtime":1771935249221,"results":"51","hashOfConfig":"45"},{"size":147,"mtime":1765450973237,"results":"52","hashOfConfig":"45"},{"size":2119,"mtime":1771956053011,"results":"53","hashOfConfig":"45"},{"size":2602,"mtime":1771949635535,"results":"54","hashOfConfig":"45"},{"size":442,"mtime":1761207744143,"results":"55","hashOfConfig":"45"},{"size":4332,"mtime":1771935524397,"results":"56","hashOfConfig":"45"},{"size":19567,"mtime":1771949868269,"results":"57","hashOfConfig":"45"},{"size":2054,"mtime":1771935506021,"results":"58","hashOfConfig":"45"},{"size":5715,"mtime":1771949904912,"results":"59","hashOfConfig":"45"},{"size":933,"mtime":1771956325630,"results":"60","hashOfConfig":"45"},{"size":1905,"mtime":1771936133654,"results":"61","hashOfConfig":"45"},{"size":354,"mtime":1771934886634,"results":"62","hashOfConfig":"45"},{"size":3909,"mtime":1772020294719,"results":"63","hashOfConfig":"45"},{"size":33826,"mtime":1772018189724,"results":"64","hashOfConfig":"45"},{"size":1655,"mtime":1771934434277,"results":"65","hashOfConfig":"45"},{"size":1864,"mtime":1771934886650,"results":"66","hashOfConfig":"45"},{"size":53605,"mtime":1771952910019,"results":"67","hashOfConfig":"45"},{"size":2343,"mtime":1771935279438,"results":"68","hashOfConfig":"45"},{"size":147,"mtime":1765450973238,"results":"69","hashOfConfig":"45"},{"size":664,"mtime":1771956053833,"results":"70","hashOfConfig":"45"},{"size":704,"mtime":1771939475570,"results":"71","hashOfConfig":"45"},{"size":354,"mtime":1771934886652,"results":"72","hashOfConfig":"45"},{"size":3106,"mtime":1771934886653,"results":"73","hashOfConfig":"45"},{"size":8591,"mtime":1771934886655,"results":"74","hashOfConfig":"45"},{"size":2626,"mtime":1771935312250,"results":"75","hashOfConfig":"45"},{"size":11895,"mtime":1771935314037,"results":"76","hashOfConfig":"45"},{"size":2910,"mtime":1771951821026,"results":"77","hashOfConfig":"45"},{"size":2939,"mtime":1771934886657,"results":"78","hashOfConfig":"45"},{"size":988,"mtime":1771937858965,"results":"79","hashOfConfig":"45"},{"size":1349,"mtime":1771937858965,"results":"80","hashOfConfig":"45"},{"size":2655,"mtime":1771935483250,"results":"81","hashOfConfig":"45"},{"size":4106,"mtime":1771935487816,"results":"82","hashOfConfig":"45"},{"size":1855,"mtime":1771951821026,"results":"83","hashOfConfig":"45"},{"size":1688,"mtime":1771951821026,"results":"84","hashOfConfig":"45"},{"size":1588,"mtime":1771949686575,"results":"85","hashOfConfig":"45"},{"size":1588,"mtime":1771949699733,"results":"86","hashOfConfig":"45"},{"size":1818,"mtime":1771949682494,"results":"87","hashOfConfig":"45"},{"filePath":"88","messages":"89","suppressedMessages":"90","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"ebiwdt",{"filePath":"91","messages":"92","suppressedMessages":"93","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"94","messages":"95","suppressedMessages":"96","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"97","messages":"98","suppressedMessages":"99","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"100","messages":"101","suppressedMessages":"102","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"103","messages":"104","suppressedMessages":"105","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"106","messages":"107","suppressedMessages":"108","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"109","messages":"110","suppressedMessages":"111","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"112","messages":"113","suppressedMessages":"114","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"115","messages":"116","suppressedMessages":"117","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"118","messages":"119","suppressedMessages":"120","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"121","messages":"122","suppressedMessages":"123","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"124","messages":"125","suppressedMessages":"126","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"127","messages":"128","suppressedMessages":"129","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"130","messages":"131","suppressedMessages":"132","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"133","messages":"134","suppressedMessages":"135","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"136","messages":"137","suppressedMessages":"138","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"139","messages":"140","suppressedMessages":"141","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"142","messages":"143","suppressedMessages":"144","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"145","messages":"146","suppressedMessages":"147","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"148","messages":"149","suppressedMessages":"150","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"151","messages":"152","suppressedMessages":"153","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"154","messages":"155","suppressedMessages":"156","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"157","messages":"158","suppressedMessages":"159","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"160","messages":"161","suppressedMessages":"162","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"163","messages":"164","suppressedMessages":"165","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"166","messages":"167","suppressedMessages":"168","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"169","messages":"170","suppressedMessages":"171","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"172","messages":"173","suppressedMessages":"174","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"175","messages":"176","suppressedMessages":"177","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"178","messages":"179","suppressedMessages":"180","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"181","messages":"182","suppressedMessages":"183","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"184","messages":"185","suppressedMessages":"186","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"187","messages":"188","suppressedMessages":"189","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"190","messages":"191","suppressedMessages":"192","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"193","messages":"194","suppressedMessages":"195","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"196","messages":"197","suppressedMessages":"198","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"199","messages":"200","suppressedMessages":"201","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"202","messages":"203","suppressedMessages":"204","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"205","messages":"206","suppressedMessages":"207","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"208","messages":"209","suppressedMessages":"210","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"211","messages":"212","suppressedMessages":"213","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"214","messages":"215","suppressedMessages":"216","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/prisma.config.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/src/app.module.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/src/main.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/src/notifications/notifications.controller.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/src/notifications/notifications.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/src/prisma/prisma.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/src/websocket/websocket.gateway.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/prisma.config.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/app.module.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapter-init.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapter-manager.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/cosmos-sdk.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/evm.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/solana.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/utxo.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/main.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/polling/swap-polling.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/prisma/prisma.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/swaps/swaps.controller.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/swaps/swaps.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/utils/affiliateFeeAsset.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/utils/pricing.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/verification/swap-verification.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/websocket/websocket.gateway.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/prisma.config.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/app.module.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/main.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/prisma/prisma.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/referral/referral.controller.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/referral/referral.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/users/users.controller.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/users/users.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/packages/shared-types/dist/index.d.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/packages/shared-types/src/index.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/packages/shared-utils/dist/index.d.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/packages/shared-utils/dist/service-clients.d.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/packages/shared-utils/src/index.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/packages/shared-utils/src/service-clients.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/near.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/starknet.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/sui.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/ton.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/tron.service.ts",[],[]] \ No newline at end of file diff --git a/apps/notifications-service/prisma.config.js b/apps/notifications-service/prisma.config.js deleted file mode 100644 index 196f9cc..0000000 --- a/apps/notifications-service/prisma.config.js +++ /dev/null @@ -1,6 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -require("dotenv/config"); -exports.default = { - schema: 'prisma/schema.prisma', -}; diff --git a/apps/notifications-service/src/app.module.ts b/apps/notifications-service/src/app.module.ts index d8e0520..e21e0b3 100644 --- a/apps/notifications-service/src/app.module.ts +++ b/apps/notifications-service/src/app.module.ts @@ -10,7 +10,7 @@ import { PrismaService } from './prisma/prisma.service'; imports: [ ConfigModule.forRoot({ isGlobal: true, - envFilePath: '../.env', + envFilePath: ['.env', '../../.env'], }), HttpModule, ], diff --git a/apps/notifications-service/src/main.ts b/apps/notifications-service/src/main.ts index b576965..d950918 100644 --- a/apps/notifications-service/src/main.ts +++ b/apps/notifications-service/src/main.ts @@ -18,10 +18,11 @@ async function bootstrap() { res.status(200).json({ status: 'ok' }); }); - const port = process.env.PORT || 3000; + const port = + process.env.NOTIFICATIONS_SERVICE_PORT || process.env.PORT || 3003; await app.listen(port); console.log(`Notifications service is running on: http://localhost:${port}`); } -bootstrap(); +void bootstrap(); diff --git a/apps/swap-service/src/app.module.ts b/apps/swap-service/src/app.module.ts index ef61cea..b046e54 100644 --- a/apps/swap-service/src/app.module.ts +++ b/apps/swap-service/src/app.module.ts @@ -13,6 +13,11 @@ import { EvmChainAdapterService } from './lib/chain-adapters/evm.service'; import { UtxoChainAdapterService } from './lib/chain-adapters/utxo.service'; import { CosmosSdkChainAdapterService } from './lib/chain-adapters/cosmos-sdk.service'; import { SolanaChainAdapterService } from './lib/chain-adapters/solana.service'; +import { TronChainAdapterService } from './lib/chain-adapters/tron.service'; +import { SuiChainAdapterService } from './lib/chain-adapters/sui.service'; +import { NearChainAdapterService } from './lib/chain-adapters/near.service'; +import { StarknetChainAdapterService } from './lib/chain-adapters/starknet.service'; +import { TonChainAdapterService } from './lib/chain-adapters/ton.service'; import { ConfigModule } from '@nestjs/config'; @Module({ @@ -20,7 +25,7 @@ import { ConfigModule } from '@nestjs/config'; ScheduleModule.forRoot(), HttpModule, ConfigModule.forRoot({ - envFilePath: '../../.env', + envFilePath: ['.env', '../../.env'], }), ], controllers: [SwapsController], @@ -36,6 +41,11 @@ import { ConfigModule } from '@nestjs/config'; UtxoChainAdapterService, CosmosSdkChainAdapterService, SolanaChainAdapterService, + TronChainAdapterService, + SuiChainAdapterService, + NearChainAdapterService, + StarknetChainAdapterService, + TonChainAdapterService, ], }) export class AppModule {} diff --git a/apps/swap-service/src/lib/chain-adapter-init.service.ts b/apps/swap-service/src/lib/chain-adapter-init.service.ts index 36dffa9..f48d6e2 100644 --- a/apps/swap-service/src/lib/chain-adapter-init.service.ts +++ b/apps/swap-service/src/lib/chain-adapter-init.service.ts @@ -4,6 +4,11 @@ import { EvmChainAdapterService } from './chain-adapters/evm.service'; import { UtxoChainAdapterService } from './chain-adapters/utxo.service'; import { CosmosSdkChainAdapterService } from './chain-adapters/cosmos-sdk.service'; import { SolanaChainAdapterService } from './chain-adapters/solana.service'; +import { TronChainAdapterService } from './chain-adapters/tron.service'; +import { SuiChainAdapterService } from './chain-adapters/sui.service'; +import { NearChainAdapterService } from './chain-adapters/near.service'; +import { StarknetChainAdapterService } from './chain-adapters/starknet.service'; +import { TonChainAdapterService } from './chain-adapters/ton.service'; @Injectable() export class ChainAdapterInitService { @@ -15,6 +20,11 @@ export class ChainAdapterInitService { private utxoChainAdapterService: UtxoChainAdapterService, private cosmosSdkChainAdapterService: CosmosSdkChainAdapterService, private solanaChainAdapterService: SolanaChainAdapterService, + private tronChainAdapterService: TronChainAdapterService, + private suiChainAdapterService: SuiChainAdapterService, + private nearChainAdapterService: NearChainAdapterService, + private starknetChainAdapterService: StarknetChainAdapterService, + private tonChainAdapterService: TonChainAdapterService, ) {} initializeChainAdapters() { @@ -29,6 +39,16 @@ export class ChainAdapterInitService { this.solanaChainAdapterService.initializeSolanaChainAdapter(); + this.tronChainAdapterService.initializeTronChainAdapter(); + + this.suiChainAdapterService.initializeSuiChainAdapter(); + + this.nearChainAdapterService.initializeNearChainAdapter(); + + this.starknetChainAdapterService.initializeStarknetChainAdapter(); + + this.tonChainAdapterService.initializeTonChainAdapter(); + this.logger.log('All chain adapters initialized successfully'); } catch (error) { this.logger.error('Failed to initialize chain adapters:', error); diff --git a/apps/swap-service/src/lib/chain-adapters/evm.service.ts b/apps/swap-service/src/lib/chain-adapters/evm.service.ts index d419ac2..d3bfccc 100644 --- a/apps/swap-service/src/lib/chain-adapters/evm.service.ts +++ b/apps/swap-service/src/lib/chain-adapters/evm.service.ts @@ -1,7 +1,33 @@ import { Injectable, Logger } from '@nestjs/common'; import { ChainAdapterManagerService } from '../chain-adapter-manager.service'; import * as unchained from '@shapeshiftoss/unchained-client'; -import { ethereum } from '@shapeshiftoss/chain-adapters'; +import { + ethereum, + monad, + hyperevm, + ink, + plasma, + mantle, + megaeth, + berachain, + cronos, + katana, + flowEvm, + celo, + plume, + story, + zksyncera, + blast, + worldchain, + hemi, + linea, + scroll, + sonic, + unichain, + bob, + mode, + soneium, +} from '@shapeshiftoss/chain-adapters'; import { ethChainId, avalancheChainId, @@ -12,13 +38,17 @@ import { arbitrumChainId, arbitrumNovaChainId, baseChainId, + monadChainId, + hyperEvmChainId, + plasmaChainId, + katanaChainId, } from '@shapeshiftoss/caip'; import { evmChainIds, type EvmChainAdapter, } from '@shapeshiftoss/chain-adapters'; import type { ChainId } from '@shapeshiftoss/caip'; -import { EvmChainId } from '@shapeshiftoss/types'; +import { EvmChainId, KnownChainIds } from '@shapeshiftoss/types'; @Injectable() export class EvmChainAdapterService { @@ -51,6 +81,54 @@ export class EvmChainAdapterService { this.initializeBaseAdapter(chainAdapterManager); + this.initializeMonadAdapter(chainAdapterManager); + + this.initializeHyperEvmAdapter(chainAdapterManager); + + this.initializeInkAdapter(chainAdapterManager); + + this.initializePlasmaAdapter(chainAdapterManager); + + this.initializeMantleAdapter(chainAdapterManager); + + this.initializeMegaEthAdapter(chainAdapterManager); + + this.initializeBerachainAdapter(chainAdapterManager); + + this.initializeCronosAdapter(chainAdapterManager); + + this.initializeKatanaAdapter(chainAdapterManager); + + this.initializeFlowEvmAdapter(chainAdapterManager); + + this.initializeCeloAdapter(chainAdapterManager); + + this.initializePlumeAdapter(chainAdapterManager); + + this.initializeStoryAdapter(chainAdapterManager); + + this.initializeZkSyncEraAdapter(chainAdapterManager); + + this.initializeBlastAdapter(chainAdapterManager); + + this.initializeWorldChainAdapter(chainAdapterManager); + + this.initializeHemiAdapter(chainAdapterManager); + + this.initializeLineaAdapter(chainAdapterManager); + + this.initializeScrollAdapter(chainAdapterManager); + + this.initializeSonicAdapter(chainAdapterManager); + + this.initializeUnichainAdapter(chainAdapterManager); + + this.initializeBobAdapter(chainAdapterManager); + + this.initializeModeAdapter(chainAdapterManager); + + this.initializeSoneiumAdapter(chainAdapterManager); + this.logger.log('All EVM chain adapters initialized successfully'); } catch (error) { this.logger.error('Failed to initialize EVM chain adapters:', error); @@ -256,6 +334,222 @@ export class EvmChainAdapterService { this.logger.log('Base adapter initialized'); } + private initializeMonadAdapter(chainAdapterManager: Map) { + const monadAdapter = new monad.ChainAdapter({ + rpcUrl: process.env.VITE_MONAD_NODE_URL || '', + getKnownTokens: () => [], + }); + chainAdapterManager.set(monadChainId, monadAdapter); + this.logger.log('Monad adapter initialized'); + } + + private initializeHyperEvmAdapter(chainAdapterManager: Map) { + const hyperEvmAdapter = new hyperevm.ChainAdapter({ + rpcUrl: process.env.VITE_HYPEREVM_NODE_URL || '', + getKnownTokens: () => [], + }); + chainAdapterManager.set(hyperEvmChainId, hyperEvmAdapter); + this.logger.log('HyperEVM adapter initialized'); + } + + private initializeInkAdapter(chainAdapterManager: Map) { + const inkAdapter = new ink.ChainAdapter({ + rpcUrl: process.env.VITE_INK_NODE_URL || '', + getKnownTokens: () => [], + }); + chainAdapterManager.set(KnownChainIds.InkMainnet, inkAdapter); + this.logger.log('Ink adapter initialized'); + } + + private initializePlasmaAdapter(chainAdapterManager: Map) { + const plasmaAdapter = new plasma.ChainAdapter({ + rpcUrl: process.env.VITE_PLASMA_NODE_URL || '', + getKnownTokens: () => [], + }); + chainAdapterManager.set(plasmaChainId, plasmaAdapter); + this.logger.log('Plasma adapter initialized'); + } + + private initializeMantleAdapter(chainAdapterManager: Map) { + const mantleAdapter = new mantle.ChainAdapter({ + rpcUrl: process.env.VITE_MANTLE_NODE_URL || '', + getKnownTokens: () => [], + }); + chainAdapterManager.set(KnownChainIds.MantleMainnet, mantleAdapter); + this.logger.log('Mantle adapter initialized'); + } + + private initializeMegaEthAdapter(chainAdapterManager: Map) { + const megaEthAdapter = new megaeth.ChainAdapter({ + rpcUrl: process.env.VITE_MEGAETH_NODE_URL || '', + getKnownTokens: () => [], + }); + chainAdapterManager.set(KnownChainIds.MegaEthMainnet, megaEthAdapter); + this.logger.log('MegaETH adapter initialized'); + } + + private initializeBerachainAdapter(chainAdapterManager: Map) { + const berachainAdapter = new berachain.ChainAdapter({ + rpcUrl: process.env.VITE_BERACHAIN_NODE_URL || '', + getKnownTokens: () => [], + }); + chainAdapterManager.set(KnownChainIds.BerachainMainnet, berachainAdapter); + this.logger.log('Berachain adapter initialized'); + } + + private initializeCronosAdapter(chainAdapterManager: Map) { + const cronosAdapter = new cronos.ChainAdapter({ + rpcUrl: process.env.VITE_CRONOS_NODE_URL || '', + getKnownTokens: () => [], + }); + chainAdapterManager.set(KnownChainIds.CronosMainnet, cronosAdapter); + this.logger.log('Cronos adapter initialized'); + } + + private initializeKatanaAdapter(chainAdapterManager: Map) { + const katanaAdapter = new katana.ChainAdapter({ + rpcUrl: process.env.VITE_KATANA_NODE_URL || '', + getKnownTokens: () => [], + }); + chainAdapterManager.set(katanaChainId, katanaAdapter); + this.logger.log('Katana adapter initialized'); + } + + private initializeFlowEvmAdapter(chainAdapterManager: Map) { + const flowEvmAdapter = new flowEvm.ChainAdapter({ + rpcUrl: process.env.VITE_FLOWEVM_NODE_URL || '', + getKnownTokens: () => [], + }); + chainAdapterManager.set(KnownChainIds.FlowEvmMainnet, flowEvmAdapter); + this.logger.log('Flow EVM adapter initialized'); + } + + private initializeCeloAdapter(chainAdapterManager: Map) { + const celoAdapter = new celo.ChainAdapter({ + rpcUrl: process.env.VITE_CELO_NODE_URL || '', + getKnownTokens: () => [], + }); + chainAdapterManager.set(KnownChainIds.CeloMainnet, celoAdapter); + this.logger.log('Celo adapter initialized'); + } + + private initializePlumeAdapter(chainAdapterManager: Map) { + const plumeAdapter = new plume.ChainAdapter({ + rpcUrl: process.env.VITE_PLUME_NODE_URL || '', + getKnownTokens: () => [], + }); + chainAdapterManager.set(KnownChainIds.PlumeMainnet, plumeAdapter); + this.logger.log('Plume adapter initialized'); + } + + private initializeStoryAdapter(chainAdapterManager: Map) { + const storyAdapter = new story.ChainAdapter({ + rpcUrl: process.env.VITE_STORY_NODE_URL || '', + getKnownTokens: () => [], + }); + chainAdapterManager.set(KnownChainIds.StoryMainnet, storyAdapter); + this.logger.log('Story adapter initialized'); + } + + private initializeZkSyncEraAdapter(chainAdapterManager: Map) { + const zkSyncEraAdapter = new zksyncera.ChainAdapter({ + rpcUrl: process.env.VITE_ZKSYNCERA_NODE_URL || '', + getKnownTokens: () => [], + }); + chainAdapterManager.set(KnownChainIds.ZkSyncEraMainnet, zkSyncEraAdapter); + this.logger.log('zkSync Era adapter initialized'); + } + + private initializeBlastAdapter(chainAdapterManager: Map) { + const blastAdapter = new blast.ChainAdapter({ + rpcUrl: process.env.VITE_BLAST_NODE_URL || '', + getKnownTokens: () => [], + }); + chainAdapterManager.set(KnownChainIds.BlastMainnet, blastAdapter); + this.logger.log('Blast adapter initialized'); + } + + private initializeWorldChainAdapter(chainAdapterManager: Map) { + const worldChainAdapter = new worldchain.ChainAdapter({ + rpcUrl: process.env.VITE_WORLDCHAIN_NODE_URL || '', + getKnownTokens: () => [], + }); + chainAdapterManager.set(KnownChainIds.WorldChainMainnet, worldChainAdapter); + this.logger.log('World Chain adapter initialized'); + } + + private initializeHemiAdapter(chainAdapterManager: Map) { + const hemiAdapter = new hemi.ChainAdapter({ + rpcUrl: process.env.VITE_HEMI_NODE_URL || '', + getKnownTokens: () => [], + }); + chainAdapterManager.set(KnownChainIds.HemiMainnet, hemiAdapter); + this.logger.log('Hemi adapter initialized'); + } + + private initializeLineaAdapter(chainAdapterManager: Map) { + const lineaAdapter = new linea.ChainAdapter({ + rpcUrl: process.env.VITE_LINEA_NODE_URL || '', + getKnownTokens: () => [], + }); + chainAdapterManager.set(KnownChainIds.LineaMainnet, lineaAdapter); + this.logger.log('Linea adapter initialized'); + } + + private initializeScrollAdapter(chainAdapterManager: Map) { + const scrollAdapter = new scroll.ChainAdapter({ + rpcUrl: process.env.VITE_SCROLL_NODE_URL || '', + getKnownTokens: () => [], + }); + chainAdapterManager.set(KnownChainIds.ScrollMainnet, scrollAdapter); + this.logger.log('Scroll adapter initialized'); + } + + private initializeSonicAdapter(chainAdapterManager: Map) { + const sonicAdapter = new sonic.ChainAdapter({ + rpcUrl: process.env.VITE_SONIC_NODE_URL || '', + getKnownTokens: () => [], + }); + chainAdapterManager.set(KnownChainIds.SonicMainnet, sonicAdapter); + this.logger.log('Sonic adapter initialized'); + } + + private initializeUnichainAdapter(chainAdapterManager: Map) { + const unichainAdapter = new unichain.ChainAdapter({ + rpcUrl: process.env.VITE_UNICHAIN_NODE_URL || '', + getKnownTokens: () => [], + }); + chainAdapterManager.set(KnownChainIds.UnichainMainnet, unichainAdapter); + this.logger.log('Unichain adapter initialized'); + } + + private initializeBobAdapter(chainAdapterManager: Map) { + const bobAdapter = new bob.ChainAdapter({ + rpcUrl: process.env.VITE_BOB_NODE_URL || '', + getKnownTokens: () => [], + }); + chainAdapterManager.set(KnownChainIds.BobMainnet, bobAdapter); + this.logger.log('BOB adapter initialized'); + } + + private initializeModeAdapter(chainAdapterManager: Map) { + const modeAdapter = new mode.ChainAdapter({ + rpcUrl: process.env.VITE_MODE_NODE_URL || '', + getKnownTokens: () => [], + }); + chainAdapterManager.set(KnownChainIds.ModeMainnet, modeAdapter); + this.logger.log('Mode adapter initialized'); + } + + private initializeSoneiumAdapter(chainAdapterManager: Map) { + const soneiumAdapter = new soneium.ChainAdapter({ + rpcUrl: process.env.VITE_SONEIUM_NODE_URL || '', + getKnownTokens: () => [], + }); + chainAdapterManager.set(KnownChainIds.SoneiumMainnet, soneiumAdapter); + this.logger.log('Soneium adapter initialized'); + } + isEvmChainAdapter(chainAdapter: unknown): chainAdapter is EvmChainAdapter { return evmChainIds.includes( (chainAdapter as EvmChainAdapter).getChainId() as EvmChainId, diff --git a/apps/swap-service/src/lib/chain-adapters/near.service.ts b/apps/swap-service/src/lib/chain-adapters/near.service.ts new file mode 100644 index 0000000..a77f345 --- /dev/null +++ b/apps/swap-service/src/lib/chain-adapters/near.service.ts @@ -0,0 +1,55 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ChainAdapterManagerService } from '../chain-adapter-manager.service'; +import { near } from '@shapeshiftoss/chain-adapters'; +import { nearChainId } from '@shapeshiftoss/caip'; +import type { ChainId } from '@shapeshiftoss/caip'; + +@Injectable() +export class NearChainAdapterService { + private readonly logger = new Logger(NearChainAdapterService.name); + + constructor(private chainAdapterManagerService: ChainAdapterManagerService) {} + + initializeNearChainAdapter() { + this.logger.log('Initializing Near chain adapter...'); + + const chainAdapterManager = + this.chainAdapterManagerService.getChainAdapterManager(); + + try { + this.initializeNearAdapter(chainAdapterManager); + this.logger.log('Near chain adapter initialized successfully'); + } catch (error) { + this.logger.error('Failed to initialize Near chain adapter:', error); + throw error; + } + } + + private initializeNearAdapter(chainAdapterManager: Map) { + const nearRpcUrls = (process.env.VITE_NEAR_NODE_URLS || '') + .split(',') + .filter(Boolean); + + const nearAdapter = new near.ChainAdapter({ + rpcUrls: + nearRpcUrls.length > 0 ? nearRpcUrls : ['https://rpc.mainnet.near.org'], + fastNearApiUrl: + process.env.VITE_NEAR_FAST_API_URL || 'https://api.fastnear.com', + }); + + chainAdapterManager.set(nearChainId, nearAdapter); + this.logger.log('Near adapter initialized'); + } + + assertGetNearChainAdapter(chainId: ChainId): near.ChainAdapter { + const chainAdapterManager = + this.chainAdapterManagerService.getChainAdapterManager(); + const adapter = chainAdapterManager.get(chainId); + + if (!adapter) { + throw new Error(`Near chain adapter not found for chain ${chainId}`); + } + + return adapter as near.ChainAdapter; + } +} diff --git a/apps/swap-service/src/lib/chain-adapters/starknet.service.ts b/apps/swap-service/src/lib/chain-adapters/starknet.service.ts new file mode 100644 index 0000000..da26bfb --- /dev/null +++ b/apps/swap-service/src/lib/chain-adapters/starknet.service.ts @@ -0,0 +1,48 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ChainAdapterManagerService } from '../chain-adapter-manager.service'; +import { starknet } from '@shapeshiftoss/chain-adapters'; +import { starknetChainId } from '@shapeshiftoss/caip'; +import type { ChainId } from '@shapeshiftoss/caip'; + +@Injectable() +export class StarknetChainAdapterService { + private readonly logger = new Logger(StarknetChainAdapterService.name); + + constructor(private chainAdapterManagerService: ChainAdapterManagerService) {} + + initializeStarknetChainAdapter() { + this.logger.log('Initializing Starknet chain adapter...'); + + const chainAdapterManager = + this.chainAdapterManagerService.getChainAdapterManager(); + + try { + this.initializeStarknetAdapter(chainAdapterManager); + this.logger.log('Starknet chain adapter initialized successfully'); + } catch (error) { + this.logger.error('Failed to initialize Starknet chain adapter:', error); + throw error; + } + } + + private initializeStarknetAdapter(chainAdapterManager: Map) { + const starknetAdapter = new starknet.ChainAdapter({ + rpcUrl: process.env.VITE_STARKNET_NODE_URL || '', + }); + + chainAdapterManager.set(starknetChainId, starknetAdapter); + this.logger.log('Starknet adapter initialized'); + } + + assertGetStarknetChainAdapter(chainId: ChainId): starknet.ChainAdapter { + const chainAdapterManager = + this.chainAdapterManagerService.getChainAdapterManager(); + const adapter = chainAdapterManager.get(chainId); + + if (!adapter) { + throw new Error(`Starknet chain adapter not found for chain ${chainId}`); + } + + return adapter as starknet.ChainAdapter; + } +} diff --git a/apps/swap-service/src/lib/chain-adapters/sui.service.ts b/apps/swap-service/src/lib/chain-adapters/sui.service.ts new file mode 100644 index 0000000..c806df5 --- /dev/null +++ b/apps/swap-service/src/lib/chain-adapters/sui.service.ts @@ -0,0 +1,48 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ChainAdapterManagerService } from '../chain-adapter-manager.service'; +import { sui } from '@shapeshiftoss/chain-adapters'; +import { suiChainId } from '@shapeshiftoss/caip'; +import type { ChainId } from '@shapeshiftoss/caip'; + +@Injectable() +export class SuiChainAdapterService { + private readonly logger = new Logger(SuiChainAdapterService.name); + + constructor(private chainAdapterManagerService: ChainAdapterManagerService) {} + + initializeSuiChainAdapter() { + this.logger.log('Initializing Sui chain adapter...'); + + const chainAdapterManager = + this.chainAdapterManagerService.getChainAdapterManager(); + + try { + this.initializeSuiAdapter(chainAdapterManager); + this.logger.log('Sui chain adapter initialized successfully'); + } catch (error) { + this.logger.error('Failed to initialize Sui chain adapter:', error); + throw error; + } + } + + private initializeSuiAdapter(chainAdapterManager: Map) { + const suiAdapter = new sui.ChainAdapter({ + rpcUrl: process.env.VITE_SUI_NODE_URL || '', + }); + + chainAdapterManager.set(suiChainId, suiAdapter); + this.logger.log('Sui adapter initialized'); + } + + assertGetSuiChainAdapter(chainId: ChainId): sui.ChainAdapter { + const chainAdapterManager = + this.chainAdapterManagerService.getChainAdapterManager(); + const adapter = chainAdapterManager.get(chainId); + + if (!adapter) { + throw new Error(`Sui chain adapter not found for chain ${chainId}`); + } + + return adapter as sui.ChainAdapter; + } +} diff --git a/apps/swap-service/src/lib/chain-adapters/ton.service.ts b/apps/swap-service/src/lib/chain-adapters/ton.service.ts new file mode 100644 index 0000000..2ea605a --- /dev/null +++ b/apps/swap-service/src/lib/chain-adapters/ton.service.ts @@ -0,0 +1,48 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ChainAdapterManagerService } from '../chain-adapter-manager.service'; +import { ton } from '@shapeshiftoss/chain-adapters'; +import { tonChainId } from '@shapeshiftoss/caip'; +import type { ChainId } from '@shapeshiftoss/caip'; + +@Injectable() +export class TonChainAdapterService { + private readonly logger = new Logger(TonChainAdapterService.name); + + constructor(private chainAdapterManagerService: ChainAdapterManagerService) {} + + initializeTonChainAdapter() { + this.logger.log('Initializing Ton chain adapter...'); + + const chainAdapterManager = + this.chainAdapterManagerService.getChainAdapterManager(); + + try { + this.initializeTonAdapter(chainAdapterManager); + this.logger.log('Ton chain adapter initialized successfully'); + } catch (error) { + this.logger.error('Failed to initialize Ton chain adapter:', error); + throw error; + } + } + + private initializeTonAdapter(chainAdapterManager: Map) { + const tonAdapter = new ton.ChainAdapter({ + rpcUrl: process.env.VITE_TON_NODE_URL || '', + }); + + chainAdapterManager.set(tonChainId, tonAdapter); + this.logger.log('Ton adapter initialized'); + } + + assertGetTonChainAdapter(chainId: ChainId): ton.ChainAdapter { + const chainAdapterManager = + this.chainAdapterManagerService.getChainAdapterManager(); + const adapter = chainAdapterManager.get(chainId); + + if (!adapter) { + throw new Error(`Ton chain adapter not found for chain ${chainId}`); + } + + return adapter as ton.ChainAdapter; + } +} diff --git a/apps/swap-service/src/lib/chain-adapters/tron.service.ts b/apps/swap-service/src/lib/chain-adapters/tron.service.ts new file mode 100644 index 0000000..4bfe8d7 --- /dev/null +++ b/apps/swap-service/src/lib/chain-adapters/tron.service.ts @@ -0,0 +1,54 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ChainAdapterManagerService } from '../chain-adapter-manager.service'; +import * as unchained from '@shapeshiftoss/unchained-client'; +import { tron } from '@shapeshiftoss/chain-adapters'; +import { tronChainId } from '@shapeshiftoss/caip'; +import type { ChainId } from '@shapeshiftoss/caip'; + +@Injectable() +export class TronChainAdapterService { + private readonly logger = new Logger(TronChainAdapterService.name); + + constructor(private chainAdapterManagerService: ChainAdapterManagerService) {} + + initializeTronChainAdapter() { + this.logger.log('Initializing Tron chain adapter...'); + + const chainAdapterManager = + this.chainAdapterManagerService.getChainAdapterManager(); + + try { + this.initializeTronAdapter(chainAdapterManager); + this.logger.log('Tron chain adapter initialized successfully'); + } catch (error) { + this.logger.error('Failed to initialize Tron chain adapter:', error); + throw error; + } + } + + private initializeTronAdapter(chainAdapterManager: Map) { + const tronHttp = new unchained.tron.TronApi({ + rpcUrl: process.env.VITE_TRON_NODE_URL || '', + }); + + const tronAdapter = new tron.ChainAdapter({ + providers: { http: tronHttp }, + rpcUrl: process.env.VITE_TRON_NODE_URL || '', + }); + + chainAdapterManager.set(tronChainId, tronAdapter); + this.logger.log('Tron adapter initialized'); + } + + assertGetTronChainAdapter(chainId: ChainId): tron.ChainAdapter { + const chainAdapterManager = + this.chainAdapterManagerService.getChainAdapterManager(); + const adapter = chainAdapterManager.get(chainId); + + if (!adapter) { + throw new Error(`Tron chain adapter not found for chain ${chainId}`); + } + + return adapter as tron.ChainAdapter; + } +} diff --git a/apps/swap-service/src/lib/chain-adapters/utxo.service.ts b/apps/swap-service/src/lib/chain-adapters/utxo.service.ts index 9887980..bdd0b0e 100644 --- a/apps/swap-service/src/lib/chain-adapters/utxo.service.ts +++ b/apps/swap-service/src/lib/chain-adapters/utxo.service.ts @@ -1,12 +1,13 @@ import { Injectable, Logger } from '@nestjs/common'; import { ChainAdapterManagerService } from '../chain-adapter-manager.service'; import * as unchained from '@shapeshiftoss/unchained-client'; -import { bitcoin } from '@shapeshiftoss/chain-adapters'; +import { bitcoin, zcash } from '@shapeshiftoss/chain-adapters'; import { btcChainId, bchChainId, dogeChainId, ltcChainId, + zecChainId, } from '@shapeshiftoss/caip'; import { utxoChainIds, @@ -32,6 +33,7 @@ export class UtxoChainAdapterService { this.initializeBitcoinCashAdapter(chainAdapterManager); this.initializeDogecoinAdapter(chainAdapterManager); this.initializeLitecoinAdapter(chainAdapterManager); + this.initializeZcashAdapter(chainAdapterManager); this.logger.log('All UTXO chain adapters initialized successfully'); } catch (error) { @@ -128,6 +130,28 @@ export class UtxoChainAdapterService { this.logger.log('Litecoin adapter initialized'); } + private initializeZcashAdapter(chainAdapterManager: Map) { + const zcashHttp = new unchained.zcash.V1Api( + new unchained.zcash.Configuration({ + basePath: process.env.VITE_UNCHAINED_ZCASH_HTTP_URL, + }), + ); + + const zcashWs = new unchained.ws.Client( + process.env.VITE_UNCHAINED_ZCASH_WS_URL, + ); + + const zcashAdapter = new zcash.ChainAdapter({ + providers: { http: zcashHttp, ws: zcashWs }, + coinName: 'Zcash', + thorMidgardUrl: process.env.VITE_THORCHAIN_MIDGARD_URL, + mayaMidgardUrl: process.env.VITE_MAYACHAIN_MIDGARD_URL, + }); + + chainAdapterManager.set(zecChainId, zcashAdapter); + this.logger.log('Zcash adapter initialized'); + } + assertGetUtxoChainAdapter(chainId: ChainId): UtxoChainAdapter { if (!utxoChainIds.includes(chainId as UtxoChainId)) { throw new Error(`Chain ${chainId} is not a UTXO chain`); diff --git a/apps/swap-service/src/main.ts b/apps/swap-service/src/main.ts index aafe667..44144db 100644 --- a/apps/swap-service/src/main.ts +++ b/apps/swap-service/src/main.ts @@ -8,7 +8,7 @@ async function bootstrap() { // Initialize chain adapters const chainAdapterInitService = app.get(ChainAdapterInitService); - await chainAdapterInitService.initializeChainAdapters(); + chainAdapterInitService.initializeChainAdapters(); // Enable CORS app.enableCors({ @@ -23,10 +23,10 @@ async function bootstrap() { res.status(200).json({ status: 'ok' }); }); - const port = process.env.PORT || 3000; + const port = process.env.SWAP_SERVICE_PORT || process.env.PORT || 3001; await app.listen(port); console.log(`Swap service is running on: http://localhost:${port}`); } -bootstrap(); +void bootstrap(); diff --git a/apps/swap-service/src/swaps/swaps.controller.ts b/apps/swap-service/src/swaps/swaps.controller.ts index 8a9d444..ec1049e 100644 --- a/apps/swap-service/src/swaps/swaps.controller.ts +++ b/apps/swap-service/src/swaps/swaps.controller.ts @@ -1,4 +1,13 @@ -import { Controller, Post, Get, Put, Param, Body, Query } from '@nestjs/common'; +import { + Controller, + Post, + Get, + Put, + Delete, + Param, + Body, + Query, +} from '@nestjs/common'; import { SwapsService } from './swaps.service'; import { SwapPollingService } from '../polling/swap-polling.service'; import { SwapVerificationService } from '../verification/swap-verification.service'; @@ -98,6 +107,21 @@ export class SwapsController { }; } + @Delete('test-cleanup') + async cleanupTestSwaps() { + const result = await this.swapsService['prisma'].swap.updateMany({ + where: { + swapId: { startsWith: 'test-' }, + status: { in: ['IDLE', 'PENDING'] }, + }, + data: { + status: 'FAILED', + statusMessage: 'Cleaned up by test runner', + }, + }); + return { cleaned: result.count }; + } + @Post(':swapId/verify-affiliate') async verifySwapAffiliate( @Param('swapId') swapId: string, diff --git a/apps/swap-service/src/swaps/swaps.service.ts b/apps/swap-service/src/swaps/swaps.service.ts index ced07d8..b17f00e 100644 --- a/apps/swap-service/src/swaps/swaps.service.ts +++ b/apps/swap-service/src/swaps/swaps.service.ts @@ -5,8 +5,17 @@ import { EvmChainAdapterService } from '../lib/chain-adapters/evm.service'; import { UtxoChainAdapterService } from '../lib/chain-adapters/utxo.service'; import { CosmosSdkChainAdapterService } from '../lib/chain-adapters/cosmos-sdk.service'; import { SolanaChainAdapterService } from '../lib/chain-adapters/solana.service'; +import { TronChainAdapterService } from '../lib/chain-adapters/tron.service'; +import { SuiChainAdapterService } from '../lib/chain-adapters/sui.service'; +import { NearChainAdapterService } from '../lib/chain-adapters/near.service'; +import { StarknetChainAdapterService } from '../lib/chain-adapters/starknet.service'; +import { TonChainAdapterService } from '../lib/chain-adapters/ton.service'; import { SwapVerificationService } from '../verification/swap-verification.service'; -import { SwapperName, swappers } from '@shapeshiftoss/swapper'; +import { + SwapperName, + swappers, + type Swap as SwapperSwap, +} from '@shapeshiftoss/swapper'; import { ChainId } from '@shapeshiftoss/caip'; import { Asset } from '@shapeshiftoss/types'; import { hashAccountId } from '@shapeshift/shared-utils'; @@ -45,6 +54,11 @@ export class SwapsService { private utxoChainAdapterService: UtxoChainAdapterService, private cosmosSdkChainAdapterService: CosmosSdkChainAdapterService, private solanaChainAdapterService: SolanaChainAdapterService, + private tronChainAdapterService: TronChainAdapterService, + private suiChainAdapterService: SuiChainAdapterService, + private nearChainAdapterService: NearChainAdapterService, + private starknetChainAdapterService: StarknetChainAdapterService, + private tonChainAdapterService: TonChainAdapterService, private swapVerificationService: SwapVerificationService, ) { this.notificationsClient = new NotificationsServiceClient(); @@ -599,7 +613,9 @@ export class SwapsService { const verificationDetails = swap.affiliateVerificationDetails as AffiliateVerificationDetails | null; - const verifiedBps = verificationDetails?.affiliateBps; + const verifiedBps = + verificationDetails?.affiliateBps ?? + (swap.affiliateBps ? parseInt(String(swap.affiliateBps)) : undefined); if (verifiedBps && sellAmountUsd > 0) { const commissionRate = this.getAffiliateCommissionRate( @@ -655,7 +671,9 @@ export class SwapsService { const verificationDetails = swap.affiliateVerificationDetails as AffiliateVerificationDetails | null; - const verifiedBps = verificationDetails?.affiliateBps; + const verifiedBps = + verificationDetails?.affiliateBps ?? + (swap.affiliateBps ? parseInt(String(swap.affiliateBps)) : undefined); if (verifiedBps && sellAmountUsd > 0) { const commissionRate = this.getAffiliateCommissionRate( @@ -724,7 +742,8 @@ export class SwapsService { verifiedBps: number, ): number { if (origin === 'web') { - return SwapsService.WEB_REVENUE_SHARE; + // Referrer gets 10bps of volume (API_BASE_BPS / verifiedBps of the fee) + return SwapsService.API_BASE_BPS / verifiedBps; } if (!origin || verifiedBps <= SwapsService.API_BASE_BPS) return 0; return (verifiedBps - SwapsService.API_BASE_BPS) / verifiedBps; @@ -789,7 +808,7 @@ export class SwapsService { id: swap.swapId, createdAt: swap.createdAt.getTime(), updatedAt: swap.updatedAt.getTime(), - }, + } as unknown as SwapperSwap, stepIndex: 0, config: { VITE_UNCHAINED_THORCHAIN_HTTP_URL: @@ -829,6 +848,17 @@ export class SwapsService { process.env.VITE_UNCHAINED_BASE_HTTP_URL || '', VITE_NEAR_INTENTS_API_KEY: process.env.VITE_NEAR_INTENTS_API_KEY || '', + VITE_BEBOP_API_KEY: process.env.VITE_BEBOP_API_KEY || '', + VITE_TENDERLY_API_KEY: process.env.VITE_TENDERLY_API_KEY || '', + VITE_TENDERLY_ACCOUNT_SLUG: + process.env.VITE_TENDERLY_ACCOUNT_SLUG || '', + VITE_TENDERLY_PROJECT_SLUG: + process.env.VITE_TENDERLY_PROJECT_SLUG || '', + VITE_TRON_NODE_URL: process.env.VITE_TRON_NODE_URL || '', + VITE_SUI_NODE_URL: process.env.VITE_SUI_NODE_URL || '', + VITE_ACROSS_API_URL: process.env.VITE_ACROSS_API_URL || '', + VITE_ACROSS_INTEGRATOR_ID: + process.env.VITE_ACROSS_INTEGRATOR_ID || '', VITE_FEATURE_THORCHAINSWAP_LONGTAIL: true, VITE_FEATURE_THORCHAINSWAP_L1_TO_LONGTAIL: true, VITE_FEATURE_CHAINFLIP_SWAP_DCA: true, @@ -851,6 +881,27 @@ export class SwapsService { assertGetEvmChainAdapter: (chainId: ChainId) => { return this.evmChainAdapterService.assertGetEvmChainAdapter(chainId); }, + assertGetTronChainAdapter: (chainId: ChainId) => { + return this.tronChainAdapterService.assertGetTronChainAdapter( + chainId, + ); + }, + assertGetSuiChainAdapter: (chainId: ChainId) => { + return this.suiChainAdapterService.assertGetSuiChainAdapter(chainId); + }, + assertGetNearChainAdapter: (chainId: ChainId) => { + return this.nearChainAdapterService.assertGetNearChainAdapter( + chainId, + ); + }, + assertGetStarknetChainAdapter: (chainId: ChainId) => { + return this.starknetChainAdapterService.assertGetStarknetChainAdapter( + chainId, + ); + }, + assertGetTonChainAdapter: (chainId: ChainId) => { + return this.tonChainAdapterService.assertGetTonChainAdapter(chainId); + }, fetchIsSmartContractAddressQuery: () => Promise.resolve(false), }); @@ -874,6 +925,10 @@ export class SwapsService { swap.expectedBuyAmountCryptoPrecision, createdAt: swap.createdAt.getTime(), sellAssetPrecision: sellAsset.precision, + affiliateBps: swap.affiliateBps, + affiliateAddress: swap.affiliateAddress, + integratorFeeRecipient: swap.affiliateAddress, + sellAmountCryptoBaseUnit: swap.sellAmountCryptoBaseUnit, }; const verificationResult = @@ -928,7 +983,12 @@ export class SwapsService { : 'PENDING', sellTxHash: swap.sellTxHash, buyTxHash: status.buyTxHash, - statusMessage: status.message, + statusMessage: + typeof status.message === 'string' + ? status.message + : Array.isArray(status.message) + ? status.message[0] + : '', isAffiliateVerified, affiliateVerificationDetails, }; diff --git a/apps/swap-service/src/verification/swap-verification.service.ts b/apps/swap-service/src/verification/swap-verification.service.ts index bfe2ab8..d43bfde 100644 --- a/apps/swap-service/src/verification/swap-verification.service.ts +++ b/apps/swap-service/src/verification/swap-verification.service.ts @@ -107,6 +107,38 @@ interface BebopTradesResponse { results?: BebopTrade[]; } +interface ButterBridgeInfo { + state?: number; + toHash?: string; + relayerHash?: string; + entrance?: string; + sourceHash?: string; + relayerChain?: { scanUrl?: string }; +} + +interface ButterBridgeInfoApiResponse { + code?: number; + data?: { + info?: ButterBridgeInfo; + }; +} + +interface AcrossDepositStatusResponse { + status?: 'filled' | 'pending' | 'expired' | 'refunded' | 'slowFillRequested'; + fillTxnRef?: string; + depositTxnRef?: string; + destinationChainId?: number; + originChainId?: number; + depositId?: number; +} + +interface StonfiQuoteMetadata { + quoteId?: string; + referrerAddress?: string; + referrerFeeUnits?: string; + referrerFeeBps?: number; +} + const THORCHAIN_PRECISION = 8; const thorchainToNativePrecision = ( @@ -190,6 +222,32 @@ export class SwapVerificationService { case 'bebop': return await this.verifyBebop(swapId, txHash, metadata); + case 'jupiter': + return await this.verifyJupiter(swapId, txHash, metadata); + + case 'arbitrum bridge': + return await this.verifyArbitrumBridge(swapId); + + case 'butterswap': + return await this.verifyButterSwap(swapId, txHash, metadata); + + case 'cetus': + return await this.verifyCetus(swapId, txHash, metadata); + + case 'sun.io': + case 'sunio': + return await this.verifySunio(swapId, txHash, metadata); + + case 'avnu': + return await this.verifyAvnu(swapId, txHash, metadata); + + case 'ston.fi': + case 'stonfi': + return await this.verifyStonfi(swapId, txHash, metadata); + + case 'across': + return await this.verifyAcross(swapId, txHash, metadata); + default: return { isVerified: false, @@ -1246,4 +1304,510 @@ export class SwapVerificationService { }; } } + + private verifyJupiter( + swapId: string, + txHash?: string, + metadata?: Record, + ): Promise { + if (!txHash) { + return Promise.resolve({ + isVerified: false, + hasAffiliate: false, + protocol: 'jupiter', + swapId, + error: 'Missing txHash for Jupiter verification', + }); + } + + try { + const referralKey = + process.env.SHAPESHIFT_JUPITER_REFERRAL_KEY || + 'Ajgmo453yGmcHDPoJBrMUj3GFwLVL7HaaZGNLkB8vREG'; + + const affiliateBps = metadata?.affiliateBps + ? parseInt(metadata.affiliateBps as string) + : undefined; + const hasAffiliate = affiliateBps !== undefined && affiliateBps > 0; + + const verifiedSellAmountCryptoBaseUnit = ( + (metadata?.sellAmountIncludingProtocolFeesCryptoBaseUnit as + | string + | undefined) ?? (metadata?.sellAmount as string | undefined) + )?.toString(); + + this.logger.log( + `Jupiter verification for swap ${swapId}: affiliateBps=${affiliateBps}, hasAffiliate=${hasAffiliate}, referralKey=${referralKey}`, + ); + + return Promise.resolve({ + isVerified: true, + hasAffiliate, + affiliateBps: hasAffiliate ? affiliateBps : undefined, + affiliateAddress: referralKey, + verifiedSellAmountCryptoBaseUnit, + protocol: 'jupiter', + swapId, + details: { + txHash, + affiliateBps: metadata?.affiliateBps as string | undefined, + referralKey, + }, + }); + } catch (error) { + this.logger.error(`Error verifying Jupiter for swap ${swapId}:`, error); + return Promise.resolve({ + isVerified: false, + hasAffiliate: false, + protocol: 'jupiter', + swapId, + error: + error instanceof Error + ? error.message + : 'Failed to verify Jupiter trade', + }); + } + } + + private verifyArbitrumBridge( + swapId: string, + ): Promise { + this.logger.log( + `ArbitrumBridge verification for swap ${swapId}: no affiliate fees supported`, + ); + + return Promise.resolve({ + isVerified: true, + hasAffiliate: false, + protocol: 'arbitrum bridge', + swapId, + details: { + note: 'ArbitrumBridge does not support affiliate fees', + }, + }); + } + + private async verifyButterSwap( + swapId: string, + txHash?: string, + metadata?: Record, + ): Promise { + if (!txHash) { + return { + isVerified: false, + hasAffiliate: false, + protocol: 'butterswap', + swapId, + error: 'Missing txHash for ButterSwap verification', + }; + } + + try { + const apiUrl = `https://bs-app-api.chainservice.io/api/queryBridgeInfoBySourceHash?hash=${txHash}`; + + this.logger.log(`ButterSwap - Fetching bridge info from API: ${apiUrl}`); + + const response = await firstValueFrom( + this.httpService.get(apiUrl), + ); + + const bridgeInfo = response.data?.data?.info; + + if (!bridgeInfo) { + return { + isVerified: false, + hasAffiliate: false, + protocol: 'butterswap', + swapId, + error: 'No bridge info found', + }; + } + + const entrance = bridgeInfo.entrance; + const shapeshiftEntrance = + process.env.SHAPESHIFT_BUTTERSWAP_ENTRANCE || 'shapeshift'; + const hasShapeshiftAffiliate = + entrance?.toLowerCase() === shapeshiftEntrance.toLowerCase(); + + const affiliateBps = metadata?.affiliateBps + ? parseInt(metadata.affiliateBps as string) + : undefined; + + const verifiedSellAmountCryptoBaseUnit = ( + (metadata?.sellAmountIncludingProtocolFeesCryptoBaseUnit as + | string + | undefined) ?? (metadata?.sellAmount as string | undefined) + )?.toString(); + + this.logger.log( + `ButterSwap verification for swap ${swapId}: entrance=${entrance}, hasAffiliate=${hasShapeshiftAffiliate}`, + ); + + return { + isVerified: true, + hasAffiliate: hasShapeshiftAffiliate, + affiliateBps: + hasShapeshiftAffiliate && affiliateBps ? affiliateBps : undefined, + verifiedSellAmountCryptoBaseUnit, + protocol: 'butterswap', + swapId, + details: { + txHash, + entrance, + bridgeInfo, + }, + }; + } catch (error) { + this.logger.error( + `Error verifying ButterSwap for swap ${swapId}:`, + error, + ); + return { + isVerified: false, + hasAffiliate: false, + protocol: 'butterswap', + swapId, + error: + error instanceof Error + ? error.message + : 'Failed to verify ButterSwap trade', + }; + } + } + + private verifyCetus( + swapId: string, + txHash?: string, + metadata?: Record, + ): Promise { + if (!txHash) { + return Promise.resolve({ + isVerified: false, + hasAffiliate: false, + protocol: 'cetus', + swapId, + error: 'Missing txHash for Cetus verification', + }); + } + + try { + const affiliateBps = metadata?.affiliateBps + ? parseInt(metadata.affiliateBps as string) + : undefined; + const hasAffiliate = affiliateBps !== undefined && affiliateBps > 0; + + const verifiedSellAmountCryptoBaseUnit = ( + (metadata?.sellAmountIncludingProtocolFeesCryptoBaseUnit as + | string + | undefined) ?? (metadata?.sellAmount as string | undefined) + )?.toString(); + + this.logger.log( + `Cetus verification for swap ${swapId}: affiliateBps=${affiliateBps}, hasAffiliate=${hasAffiliate}`, + ); + + return Promise.resolve({ + isVerified: true, + hasAffiliate, + affiliateBps: hasAffiliate ? affiliateBps : undefined, + verifiedSellAmountCryptoBaseUnit, + protocol: 'cetus', + swapId, + details: { + txHash, + affiliateBps: metadata?.affiliateBps as string | undefined, + }, + }); + } catch (error) { + this.logger.error(`Error verifying Cetus for swap ${swapId}:`, error); + return Promise.resolve({ + isVerified: false, + hasAffiliate: false, + protocol: 'cetus', + swapId, + error: + error instanceof Error + ? error.message + : 'Failed to verify Cetus trade', + }); + } + } + + private verifySunio( + swapId: string, + txHash?: string, + metadata?: Record, + ): Promise { + if (!txHash) { + return Promise.resolve({ + isVerified: false, + hasAffiliate: false, + protocol: 'sun.io', + swapId, + error: 'Missing txHash for Sun.io verification', + }); + } + + try { + const affiliateBps = metadata?.affiliateBps + ? parseInt(metadata.affiliateBps as string) + : undefined; + const hasAffiliate = affiliateBps !== undefined && affiliateBps > 0; + + const verifiedSellAmountCryptoBaseUnit = ( + (metadata?.sellAmountIncludingProtocolFeesCryptoBaseUnit as + | string + | undefined) ?? (metadata?.sellAmount as string | undefined) + )?.toString(); + + this.logger.log( + `Sun.io verification for swap ${swapId}: affiliateBps=${affiliateBps}, hasAffiliate=${hasAffiliate}`, + ); + + return Promise.resolve({ + isVerified: true, + hasAffiliate, + affiliateBps: hasAffiliate ? affiliateBps : undefined, + verifiedSellAmountCryptoBaseUnit, + protocol: 'sun.io', + swapId, + details: { + txHash, + affiliateBps: metadata?.affiliateBps as string | undefined, + }, + }); + } catch (error) { + this.logger.error(`Error verifying Sun.io for swap ${swapId}:`, error); + return Promise.resolve({ + isVerified: false, + hasAffiliate: false, + protocol: 'sun.io', + swapId, + error: + error instanceof Error + ? error.message + : 'Failed to verify Sun.io trade', + }); + } + } + + private verifyAvnu( + swapId: string, + txHash?: string, + metadata?: Record, + ): Promise { + if (!txHash) { + return Promise.resolve({ + isVerified: false, + hasAffiliate: false, + protocol: 'avnu', + swapId, + error: 'Missing txHash for AVNU verification', + }); + } + + try { + const affiliateBps = metadata?.affiliateBps + ? parseInt(metadata.affiliateBps as string) + : undefined; + const hasAffiliate = affiliateBps !== undefined && affiliateBps > 0; + const affiliateAddress = metadata?.integratorFeeRecipient as + | string + | undefined; + + const verifiedSellAmountCryptoBaseUnit = ( + (metadata?.sellAmountIncludingProtocolFeesCryptoBaseUnit as + | string + | undefined) ?? (metadata?.sellAmount as string | undefined) + )?.toString(); + + this.logger.log( + `AVNU verification for swap ${swapId}: affiliateBps=${affiliateBps}, hasAffiliate=${hasAffiliate}, integratorFeeRecipient=${affiliateAddress}`, + ); + + return Promise.resolve({ + isVerified: true, + hasAffiliate, + affiliateBps: hasAffiliate ? affiliateBps : undefined, + affiliateAddress, + verifiedSellAmountCryptoBaseUnit, + protocol: 'avnu', + swapId, + details: { + txHash, + affiliateBps: metadata?.affiliateBps as string | undefined, + integratorFeeRecipient: metadata?.integratorFeeRecipient as + | string + | undefined, + }, + }); + } catch (error) { + this.logger.error(`Error verifying AVNU for swap ${swapId}:`, error); + return Promise.resolve({ + isVerified: false, + hasAffiliate: false, + protocol: 'avnu', + swapId, + error: + error instanceof Error + ? error.message + : 'Failed to verify AVNU trade', + }); + } + } + + private verifyStonfi( + swapId: string, + txHash?: string, + metadata?: Record, + ): Promise { + if (!txHash) { + return Promise.resolve({ + isVerified: false, + hasAffiliate: false, + protocol: 'ston.fi', + swapId, + error: 'Missing txHash for STON.fi verification', + }); + } + + try { + const stonfiSpecific = metadata?.stonfiSpecific as + | StonfiQuoteMetadata + | undefined; + + const referrerAddress = stonfiSpecific?.referrerAddress; + const referrerFeeUnits = stonfiSpecific?.referrerFeeUnits; + + const affiliateBps = metadata?.affiliateBps + ? parseInt(metadata.affiliateBps as string) + : (stonfiSpecific?.referrerFeeBps ?? undefined); + + const hasAffiliate = + !!referrerAddress && + (affiliateBps !== undefined ? affiliateBps > 0 : false); + + const verifiedSellAmountCryptoBaseUnit = ( + (metadata?.sellAmountIncludingProtocolFeesCryptoBaseUnit as + | string + | undefined) ?? (metadata?.sellAmount as string | undefined) + )?.toString(); + + this.logger.log( + `STON.fi verification for swap ${swapId}: affiliateBps=${affiliateBps}, hasAffiliate=${hasAffiliate}, referrerAddress=${referrerAddress}`, + ); + + return Promise.resolve({ + isVerified: true, + hasAffiliate, + affiliateBps: hasAffiliate ? affiliateBps : undefined, + affiliateAddress: referrerAddress, + verifiedSellAmountCryptoBaseUnit, + protocol: 'ston.fi', + swapId, + details: { + txHash, + referrerAddress, + referrerFeeUnits, + stonfiSpecific: metadata?.stonfiSpecific as + | Record + | undefined, + }, + }); + } catch (error) { + this.logger.error(`Error verifying STON.fi for swap ${swapId}:`, error); + return Promise.resolve({ + isVerified: false, + hasAffiliate: false, + protocol: 'ston.fi', + swapId, + error: + error instanceof Error + ? error.message + : 'Failed to verify STON.fi trade', + }); + } + } + + private async verifyAcross( + swapId: string, + txHash?: string, + metadata?: Record, + ): Promise { + if (!txHash) { + return { + isVerified: false, + hasAffiliate: false, + protocol: 'across', + swapId, + error: 'Missing txHash for Across verification', + }; + } + + try { + const acrossApiUrl = + process.env.VITE_ACROSS_API_URL || 'https://app.across.to/api'; + const statusUrl = `${acrossApiUrl}/deposit/status?depositTxnRef=${txHash}`; + + this.logger.log( + `Across - Fetching deposit status from API: ${statusUrl}`, + ); + + const response = await firstValueFrom( + this.httpService.get(statusUrl), + ); + + const depositStatus = response.data; + + const affiliateBps = metadata?.affiliateBps + ? parseInt(metadata.affiliateBps as string) + : undefined; + const hasAffiliate = affiliateBps !== undefined && affiliateBps > 0; + + const affiliateAddress = + (metadata?.appFeeRecipient as string | undefined) || + (metadata?.integratorId as string | undefined); + + const fillTxnRef = depositStatus?.fillTxnRef; + + const verifiedSellAmountCryptoBaseUnit = ( + (metadata?.sellAmountIncludingProtocolFeesCryptoBaseUnit as + | string + | undefined) ?? (metadata?.sellAmount as string | undefined) + )?.toString(); + + this.logger.log( + `Across verification for swap ${swapId}: status=${depositStatus?.status}, hasAffiliate=${hasAffiliate}, affiliateBps=${affiliateBps}`, + ); + + return { + isVerified: true, + hasAffiliate, + affiliateBps: hasAffiliate ? affiliateBps : undefined, + affiliateAddress, + verifiedSellAmountCryptoBaseUnit, + protocol: 'across', + swapId, + details: { + txHash, + fillTxnRef, + depositStatus, + integratorId: metadata?.integratorId as string | undefined, + appFeeRecipient: metadata?.appFeeRecipient as string | undefined, + }, + }; + } catch (error) { + this.logger.error(`Error verifying Across for swap ${swapId}:`, error); + return { + isVerified: false, + hasAffiliate: false, + protocol: 'across', + swapId, + error: + error instanceof Error + ? error.message + : 'Failed to verify Across deposit', + }; + } + } } diff --git a/apps/user-service/prisma.config.js b/apps/user-service/prisma.config.js deleted file mode 100644 index 196f9cc..0000000 --- a/apps/user-service/prisma.config.js +++ /dev/null @@ -1,6 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -require("dotenv/config"); -exports.default = { - schema: 'prisma/schema.prisma', -}; diff --git a/apps/user-service/src/app.module.ts b/apps/user-service/src/app.module.ts index cf7e6e0..679da63 100644 --- a/apps/user-service/src/app.module.ts +++ b/apps/user-service/src/app.module.ts @@ -10,7 +10,7 @@ import { PrismaService } from './prisma/prisma.service'; imports: [ ConfigModule.forRoot({ isGlobal: true, - envFilePath: '../../.env', + envFilePath: ['.env', '../../.env'], }), ], controllers: [UsersController, ReferralController], diff --git a/apps/user-service/src/main.ts b/apps/user-service/src/main.ts index 9758d7e..f042065 100644 --- a/apps/user-service/src/main.ts +++ b/apps/user-service/src/main.ts @@ -18,10 +18,10 @@ async function bootstrap() { res.status(200).json({ status: 'ok' }); }); - const port = process.env.PORT || 3000; + const port = process.env.USER_SERVICE_PORT || process.env.PORT || 3002; await app.listen(port); console.log(`User service is running on: http://localhost:${port}`); } -bootstrap(); +void bootstrap(); diff --git a/package.json b/package.json index 4abb430..a8efc17 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ }, "dependencies": { "@bitcoinerlab/secp256k1": "^1.1.1", + "@cetusprotocol/aggregator-sdk": "^1.4.5", "@defuse-protocol/one-click-sdk-typescript": "^0.1.1-0.2", "@metamask/eth-sig-util": "^7.0.0", "@nestjs/axios": "^4.0.1", @@ -46,16 +47,17 @@ "@pulumi/kubernetes": "4.23.0", "@pulumi/pulumi": "3.160.0", "@shapeshiftoss/caip": "8.16.7", - "@shapeshiftoss/chain-adapters": "11.3.6", - "@shapeshiftoss/swapper": "17.6.7", - "@shapeshiftoss/types": "8.6.5", - "@shapeshiftoss/unchained-client": "10.14.8", + "@shapeshiftoss/chain-adapters": "11.3.9", + "@shapeshiftoss/swapper": "17.6.10", + "@shapeshiftoss/types": "8.6.7", + "@shapeshiftoss/unchained-client": "10.14.10", "@shapeshiftoss/unchained-pulumi": "1.0.2", "axios": "^1.7.4", "dotenv": "^17.2.2", "p-lazy": "3.1.0", "protobufjs": "^7.5.4", "socket.io": "^4.8.1", + "starknet": "9.2.1", "tronweb": "^6.0.0" }, "devDependencies": { diff --git a/tests/TESTING.md b/tests/TESTING.md new file mode 100644 index 0000000..57395d7 --- /dev/null +++ b/tests/TESTING.md @@ -0,0 +1,396 @@ +# Comprehensive Swapper Testing Guide + +This guide covers how to run the full swapper test suite that validates all 18 swappers' `checkTradeStatus` implementations, affiliate fee verification, and the polling lifecycle. + +## Prerequisites + +### Services Running + +All 3 backend NestJS services must be running: + +```bash +# From shapeshift-backend root +yarn start:dev +``` + +| Service | Port | Database | +|---------|------|----------| +| swap-service | 3001 | `swap_service` (PostgreSQL) | +| user-service | 3002 | `user_service` (PostgreSQL) | +| notifications-service | 3003 | `notifications_service` (PostgreSQL) | + +PostgreSQL must be running on `localhost:5432` (Docker). + +### Health Check + +```bash +curl http://localhost:3001/swaps/pending +# Should return a JSON array (empty or with existing pending swaps) +``` + +## Running the Test Script + +```bash +cd /Users/0xm4king/Projects/shapeshift-backend +node tests/test-all-swappers.mjs +``` + +The script runs 4 phases: +1. **Cleanup** — Marks old `test-*` swaps as FAILED via `DELETE /swaps/test-cleanup` to clear the polling queue +2. **Create** — POSTs fake pending swaps to `POST /swaps` for each swapper +3. **Poll** — Polls ALL swaps in parallel for up to 2 minutes total +4. **Report** — Prints pass/fail summary with affiliate verification status + +### Expected Runtime + +~2-3 minutes total. Most swaps resolve in the first 3 poll cycles (18 seconds). The remaining time is spent waiting for expected-PENDING swaps to time out. + +### Expected Output + +``` +✓ SUCCESS: 14 (4 affiliate verified) +✗ FAILED: 0 +◌ STUCK (expected): 3 +⚠ STUCK (unexpected): 0 +⊘ SKIPPED: 0 +✗ CREATE FAILED: 0 +TOTAL: 17/18 +``` + +The 18th swapper is `Test` (internal, not tested). + +## What the Test Validates + +For each swapper, the test verifies: + +1. **Swap creation** — `POST /swaps` accepts the payload without error +2. **Status polling** — The 5-second cron job picks up the swap and calls `checkTradeStatus` +3. **Status resolution** — The swap transitions from `PENDING` to `SUCCESS` (or `FAILED`) +4. **Affiliate verification** — `isAffiliateVerified` is set and `affiliateVerificationDetails` is populated +5. **Affiliate bps** — For metadata-based verifiers, `affiliateBps` matches the input (60) + +## Swapper Coverage Matrix + +### Resolves to SUCCESS (14 swappers) + +| Swapper | Status Check Method | Test Tx Type | Affiliate Verified | Notes | +|---------|--------------------|--------------|--------------------|-------| +| THORChain | Midgard API (`/thorchain/tx/{hash}`) | Real BTC swap tx | No (not a real affiliate tx) | Uses `VITE_THORCHAIN_NODE_URL` | +| MAYAChain | Midgard API (`/mayachain/tx/{hash}`) | Real ZEC swap tx | No | Uses `VITE_MAYACHAIN_NODE_URL` | +| CowSwap | CoW Protocol API (`/v1/trades?orderUid=`) | Real orderUid | No | `sellTxHash` IS the orderUid | +| 0x (Zrx) | `checkEvmSwapStatus` (unchained) | Any confirmed ETH tx | No | Simple on-chain confirmation | +| Portals | `checkEvmSwapStatus` (unchained) | Any confirmed ETH tx | No | Simple on-chain confirmation | +| Bebop | `checkEvmSwapStatus` (unchained) | Any confirmed ETH tx | No | Simple on-chain confirmation | +| Jupiter | `checkSolanaSwapStatus` (unchained) | Successful Solana tx | Yes (bps=60) | MUST be a successful tx (no `transactionError`) | +| AVNU | `checkStarknetSwapStatus` (Starknet RPC) | Real Starknet tx | Yes (bps=60) | Uses `VITE_STARKNET_NODE_URL` | +| Sun.io | `checkTronSwapStatus` (Tron RPC) | Real Tron tx | Yes (bps=60) | Uses `VITE_TRON_NODE_URL` | +| Cetus | `checkSuiSwapStatus` (Sui RPC) | Confirmed Sui tx digest | Yes (bps=60) | Requires `receiveAddress` on swap | +| Relay | Relay API (`/intents/status/v2?requestId=`) | Real Relay requestId | No (bps=85 from Relay) | Requires `metadata.relayTransactionMetadata.relayId` | +| Arbitrum Bridge | `checkEvmSwapStatus` (unchained) | Confirmed Arbitrum tx | No | L2→L1 withdraw path returns SUCCESS on tx confirmation | +| ButterSwap | `checkEvmSwapStatus` (unchained) | Any confirmed ETH tx | No | Same-chain EVM path, no bridge indexer needed | +| NEAR Intents | 1Click API (`/v0/status?depositAddress=`) | Real completed deposit | No (bps=25 from real tx) | Requires `metadata.nearIntentsSpecific.depositAddress` | + +### Expected to Stay PENDING (3 swappers) + +| Swapper | Reason | How to Fix | +|---------|--------|------------| +| STON.fi | TON chain adapter `parseTx()` needs sender address; falls back when no `quoteId` | Provide real `quoteId` from STON.fi/Omniston SDK | +| Across | Across deposit status API won't recognize a random ETH tx as a valid deposit | Use a real Across bridge deposit tx hash | +| Chainflip | Broker API returns 404 for unknown swapId; the API key is broker-specific | Use a real swap ID created through this specific broker | + +### Not Tested + +| Swapper | Reason | +|---------|--------| +| Test | Internal test swapper, no real implementation | + +## Affiliate Verification Behavior + +There are two types of affiliate verification: + +### On-chain Verification (THORChain, MAYAChain, CowSwap, 0x, Portals, Bebop, Relay, Arbitrum Bridge, ButterSwap) +These check the actual transaction on-chain or via protocol APIs for affiliate fee data. Test txs are NOT real ShapeShift affiliate swaps, so they correctly report `hasAffiliate=false`. This is **expected behavior** — the verification logic works, it just doesn't find affiliate data in random txs. + +### Metadata-based Verification (Jupiter, AVNU, Sun.io, Cetus, STON.fi) +These check the swap's `affiliateBps` from the enriched metadata passed during verification. Since we set `affiliateBps=60` in the test payload, these correctly report `hasAffiliate=true, affiliateBps=60`. + +### API-based Verification (NEAR Intents, Relay) +These read affiliate data from the external API response. The real NEAR Intents deposit shows `bps=25` (the actual appFee from that transaction). Relay shows `bps=85` from the Relay API. + +## Updating Test Data + +### When Tx Hashes Expire or Stop Working + +Some tx hashes may stop working over time (pruned from RPC nodes, API changes). To update: + +1. **EVM txs (0x, Portals, Bebop, ButterSwap, Arbitrum Bridge)**: Any confirmed mainnet tx works: + ```bash + # Ethereum + curl -s 'https://api.ethereum.shapeshift.com/api/v1/tx/0x' + # Arbitrum + curl -s 'https://api.arbitrum.shapeshift.com/api/v1/tx/0x' + # Should return JSON with "status": 1 + ``` + +2. **Solana tx (Jupiter)**: Must be a SUCCESSFUL tx (no `transactionError`): + ```bash + # Find a recent successful Jupiter tx + curl -s -X POST "https://api.solana.shapeshift.com/api/v1/jsonrpc" \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"getSignaturesForAddress","params":["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4",{"limit":30}]}' + # Pick one where "err": null + + # Verify it's successful + curl -s "https://api.solana.shapeshift.com/api/v1/tx/" + # Should have "transactionError": null + ``` + +3. **Starknet tx (AVNU)**: Any confirmed Starknet tx: + ```bash + curl -s -X POST "https://rpc.starknet.lava.build" \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"starknet_getTransactionReceipt","params":["0x"]}' + # Should show execution_status: "SUCCEEDED" + ``` + +4. **Tron tx (Sun.io)**: Any confirmed Tron tx: + ```bash + curl -s "https://api.trongrid.io/walletsolidity/gettransactioninfobyid" \ + -X POST -H "Content-Type: application/json" \ + -d '{"value":""}' + # Should return receipt with result: "SUCCESS" + ``` + +5. **Sui tx digest (Cetus)**: Any confirmed Sui tx: + ```bash + curl -s -X POST https://fullnode.mainnet.sui.io:443 \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"sui_getCheckpoint","params":["latest"]}' + # Get a tx digest from the transactions array + ``` + +6. **THORChain tx**: Must be a real THORChain swap visible on Midgard: + ```bash + curl -s "https://thornode.ninerealms.com/thorchain/tx/" + # Should return observed_tx with swap data + ``` + +7. **MAYAChain tx**: Same as THORChain but on MAYAChain Midgard: + ```bash + curl -s "https://mayanode.mayachain.info/mayachain/tx/" + ``` + +8. **CowSwap orderUid**: Get from CoW Protocol API: + ```bash + curl -s "https://api.cow.fi/mainnet/api/v1/orders/" + # Should return order with status: "fulfilled" + ``` + +9. **Relay requestId**: Get from Relay API: + ```bash + curl -s "https://api.relay.link/intents/status/v2?requestId=" + # Should return status data + ``` + +10. **NEAR Intents depositAddress**: Get from 1Click API (completed swaps): + ```bash + # Browse https://explorer.near-intents.org for recent completed transactions + # Copy the deposit address from a completed swap + # Verify: + curl -s "https://1click.chaindefuser.com/v0/status?depositAddress=" + # Should return { "status": "SUCCESS", ... } + ``` + +## Environment Variables That Affect Tests + +These `.env` values in the backend root must point to working endpoints: + +```env +# Critical for THORChain/MAYAChain (daemon URLs that resolve) +VITE_THORCHAIN_NODE_URL="https://thornode.ninerealms.com" +VITE_MAYACHAIN_NODE_URL="https://mayanode.mayachain.info" + +# Critical for EVM swappers +VITE_UNCHAINED_ETHEREUM_HTTP_URL="https://api.ethereum.shapeshift.com" + +# Critical for Solana/Jupiter +VITE_UNCHAINED_SOLANA_HTTP_URL="https://api.solana.shapeshift.com" + +# Critical for AVNU/Starknet +VITE_STARKNET_NODE_URL="https://rpc.starknet.lava.build" + +# Critical for Sun.io/Tron +VITE_TRON_NODE_URL="https://api.trongrid.io" + +# Critical for Cetus/Sui +VITE_SUI_NODE_URL="https://fullnode.mainnet.sui.io" + +# Critical for STON.fi/TON +VITE_TON_NODE_URL="https://toncenter.com/api/v2/jsonRPC" + +# Critical for CowSwap +VITE_COWSWAP_BASE_URL="https://api.cow.fi" + +# Critical for Relay +VITE_RELAY_API_URL="https://api.relay.link" + +# Critical for Across +VITE_ACROSS_API_URL="https://app.across.to/api" + +# Critical for Chainflip +VITE_CHAINFLIP_API_URL="https://chainflip-broker.io" +VITE_CHAINFLIP_API_KEY="09bc0796ff40435482c0a54fa6ae2784" + +# Critical for NEAR Intents +VITE_NEAR_INTENTS_API_KEY="" +``` + +If any URL is dead (DNS failure, 5xx, etc.), the corresponding swapper will stay `PENDING` indefinitely. + +## Swap Creation DTO Reference + +Every test swap requires these fields: + +```typescript +{ + swapId: string; // Unique ID (test script generates: "test-{swapper}-{timestamp}") + sellAsset: Asset; // { chainId, assetId, symbol, precision, name } + buyAsset: Asset; // Same structure + sellTxHash: string; // REQUIRED for polling to pick up the swap + sellAmountCryptoBaseUnit: string; + expectedBuyAmountCryptoBaseUnit: string; + sellAmountCryptoPrecision: string; + expectedBuyAmountCryptoPrecision: string; + source: string; // "test-script" + swapperName: string; // Must match SwapperName enum display value + sellAccountId: string; // Gets hashed by hashAccountId() + receiveAddress?: string; // REQUIRED for Cetus, Solana — some swappers use it + metadata?: Record; // Relay needs relayTransactionMetadata here + affiliateAddress?: string; // Our affiliate address for verification + affiliateBps?: string; // "60" — the affiliate fee in basis points + origin?: 'web' | 'api' | 'widget'; // Affects commission rate calculation +} +``` + +### SwapperName Values (exact strings) + +``` +THORChain, MAYAChain, CoW Swap, 0x, Portals, Chainflip, +Jupiter, Relay, ButterSwap, Bebop, NEAR Intents, Cetus, +Sun.io, AVNU, STON.fi, Across, Arbitrum Bridge, Test +``` + +### Metadata Requirements per Swapper + +| Swapper | Required Metadata | Example | +|---------|-------------------|---------| +| Relay | `relayTransactionMetadata.relayId` | `{ relayTransactionMetadata: { relayId: "0xabc..." } }` | +| Chainflip | `chainflipSwapId` (integer) | `{ chainflipSwapId: 12345 }` | +| NEAR Intents | `nearIntentsSpecific.depositAddress` | `{ nearIntentsSpecific: { depositAddress: "1Q7c..." } }` | +| All others | None required | `{}` | + +## Polling Behavior + +- Polls every **5 seconds** via `@Cron(CronExpression.EVERY_5_SECONDS)` +- Only polls swaps with `status IN ('IDLE', 'PENDING')` AND `sellTxHash IS NOT NULL` +- No retry limits or age limits — polls indefinitely +- Affiliate verification runs on **every poll cycle** +- On error: returns `status: 'PENDING'` (doesn't fail the swap) + +## Cleanup Endpoint + +The test script uses `DELETE /swaps/test-cleanup` to mark old test swaps as FAILED before creating new ones. This prevents old swaps from clogging the polling queue. + +```bash +# Manual cleanup +curl -X DELETE http://localhost:3001/swaps/test-cleanup +# Returns: { "cleaned": N } +``` + +## Troubleshooting + +### Swap stays PENDING forever + +1. Check `sellTxHash` is set (null = never polled) +2. Check backend logs: `tmux capture-pane -t backend -p -S -200 | grep "test-{swapper}"` +3. Check the env URL for that chain's RPC/API is resolving +4. Verify the tx hash is valid and confirmed on the respective chain + +### Swap goes to FAILED + +1. The on-chain tx actually failed (check `transactionError` field) +2. The chain adapter threw an unrecoverable error +3. The API returned a definitive "failed" status (e.g., CowSwap order cancelled) + +### Affiliate shows NOT_VERIFIED for metadata-based swappers + +Check that `affiliateBps` and `affiliateAddress` are set in the CreateSwapDto. The enriched metadata in `pollSwapStatus` reads these from the DB record: + +```typescript +affiliateBps: swap.affiliateBps, +affiliateAddress: swap.affiliateAddress, +integratorFeeRecipient: swap.affiliateAddress, +sellAmountCryptoBaseUnit: swap.sellAmountCryptoBaseUnit, +``` + +### DNS errors (ENOTFOUND) + +A daemon/node URL is dead. Update in `.env` and restart the backend: + +```bash +# Check DNS resolution +curl -sv --max-time 5 "https://the-url.com" 2>&1 | head -5 + +# Find working alternatives for THORChain/MAYAChain: +# THORChain: https://thornode.ninerealms.com +# MAYAChain: https://mayanode.mayachain.info +``` + +### Old test swaps clogging polling queue + +If old test swaps are slowing down polling (24+ pending swaps), cleanup before running: + +```bash +curl -X DELETE http://localhost:3001/swaps/test-cleanup +``` + +## Architecture Reference + +``` +test-all-swappers.mjs + | + +-- DELETE /swaps/test-cleanup (mark old test-* swaps as FAILED) + | + +-- POST /swaps (swap-service:3001) + | | + | +-- swaps.controller.ts -> swaps.service.ts createSwap() + | | + | +-- Prisma insert into swap_service.swaps table + | + +-- [5s cron] swap-polling.service.ts pollPendingSwaps() + | + +-- swaps.service.ts pollSwapStatus(swapId) + | + +-- swappers[swapperName].checkTradeStatus({...}) + | | + | +-- [per-swapper implementation in @shapeshiftoss/swapper] + | + +-- swapVerificationService.verifySwapAffiliate(...) + | | + | +-- [per-swapper verification in swap-verification.service.ts] + | + +-- Prisma update (status, affiliateVerification, buyTxHash) +``` + +## Key Files + +| File | Purpose | +|------|---------| +| `tests/test-all-swappers.mjs` | The test script | +| `apps/swap-service/src/swaps/swaps.service.ts` | Swap creation + pollSwapStatus | +| `apps/swap-service/src/swaps/swaps.controller.ts` | REST endpoints including test-cleanup | +| `apps/swap-service/src/polling/swap-polling.service.ts` | 5s cron loop | +| `apps/swap-service/src/verification/swap-verification.service.ts` | All 18 affiliate verifiers | +| `apps/swap-service/prisma/schema.prisma` | Swap model (53 columns) | +| `packages/shared-types/src/index.ts` | CreateSwapDto, UpdateSwapStatusDto | +| `.env` | All RPC/API URLs | diff --git a/tests/test-all-swappers.mjs b/tests/test-all-swappers.mjs new file mode 100644 index 0000000..d46ae2c --- /dev/null +++ b/tests/test-all-swappers.mjs @@ -0,0 +1,776 @@ +#!/usr/bin/env node + +/** + * Comprehensive Swapper Test Script + * + * Creates fake pending swaps for all 18 swappers using real confirmed tx hashes, + * then waits for polling to resolve them and verifies: + * 1. Status transitions (PENDING → SUCCESS) + * 2. Affiliate verification (isAffiliateVerified = true) + * 3. Affiliate fee fields populated + * + * Usage: node tests/test-all-swappers.mjs + */ + +const SWAP_SERVICE_URL = 'http://localhost:3001'; +const POLL_INTERVAL_MS = 6_000; // slightly > 5s poll interval +const MAX_POLL_ATTEMPTS = 20; // 2 minutes max wait per swap +const AFFILIATE_ADDRESS = '0xc770eefad204b5180df6a14ee197d99d808ee52d'; // ShapeShift DAO treasury +const AFFILIATE_BPS = '60'; + +// Dummy receive addresses per chain (valid format, used by some checkTradeStatus impls) +const RECEIVE_ADDRESSES = { + ETH: '0x0000000000000000000000000000000000000001', + SOL: '11111111111111111111111111111111', // Solana system program + SUI: '0x0000000000000000000000000000000000000000000000000000000000000001', + TON: 'EQBynBO23ywHy_CgarY9NK9FTz0yDsG82PtcbSTQgGoXwiuA', + STRK: '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7', + TRX: 'TLsV52sRDL79HXGGm9yzwKibb6BeruhUzy', + BTC: 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4', + ZEC: 't1Hsc1LR8yKnbbe3twRp88p6vFfC5t7DLbs', + ARB: '0x0000000000000000000000000000000000000001', +}; + +// ─── Helpers ────────────────────────────────────────────────────────────────── + +function makeSwapId(swapperKey) { + return `test-${swapperKey.toLowerCase()}-${Date.now()}`; +} + +async function createSwap(payload) { + const res = await fetch(`${SWAP_SERVICE_URL}/swaps`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }); + if (!res.ok) { + const body = await res.text(); + throw new Error(`POST /swaps failed (${res.status}): ${body}`); + } + return res.json(); +} + +async function getSwap(swapId) { + const res = await fetch(`${SWAP_SERVICE_URL}/swaps/${swapId}`); + if (!res.ok) return null; + return res.json(); +} + +function sleep(ms) { + return new Promise((r) => setTimeout(r, ms)); +} + +// ─── Asset Factories ────────────────────────────────────────────────────────── + +const assets = { + BTC: { + chainId: 'bip122:000000000019d6689c085ae165831e93', + assetId: 'bip122:000000000019d6689c085ae165831e93/slip44:0', + symbol: 'BTC', + precision: 8, + name: 'Bitcoin', + }, + ZEC: { + chainId: 'bip122:00040fe8ec8471911baa1db1266ea15d', + assetId: 'bip122:00040fe8ec8471911baa1db1266ea15d/slip44:133', + symbol: 'ZEC', + precision: 8, + name: 'Zcash', + }, + ETH: { + chainId: 'eip155:1', + assetId: 'eip155:1/slip44:60', + symbol: 'ETH', + precision: 18, + name: 'Ethereum', + }, + USDC_ETH: { + chainId: 'eip155:1', + assetId: 'eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + symbol: 'USDC', + precision: 6, + name: 'USD Coin', + }, + WETH: { + chainId: 'eip155:1', + assetId: 'eip155:1/erc20:0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + symbol: 'WETH', + precision: 18, + name: 'Wrapped Ether', + }, + PAPER: { + chainId: 'eip155:1', + assetId: 'eip155:1/erc20:0x7ae1d57b58fa6411f32948314badd83583ee0e8c', + symbol: 'PAPER', + precision: 18, + name: 'Paper', + }, + BNB: { + chainId: 'eip155:56', + assetId: 'eip155:56/slip44:714', + symbol: 'BNB', + precision: 18, + name: 'BNB', + }, + SOL: { + chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + assetId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501', + symbol: 'SOL', + precision: 9, + name: 'Solana', + }, + USDC_SOL: { + chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + assetId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', + symbol: 'USDC', + precision: 6, + name: 'USD Coin', + }, + STRK: { + chainId: 'starknet:SN_MAIN', + assetId: 'starknet:SN_MAIN/erc20:0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d', + symbol: 'STRK', + precision: 18, + name: 'Starknet', + }, + USDC_STRK: { + chainId: 'starknet:SN_MAIN', + assetId: 'starknet:SN_MAIN/erc20:0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8', + symbol: 'USDC', + precision: 6, + name: 'USD Coin', + }, + TRX: { + chainId: 'tron:0x2b6653dc', + assetId: 'tron:0x2b6653dc/slip44:195', + symbol: 'TRX', + precision: 6, + name: 'TRON', + }, + USDT_TRX: { + chainId: 'tron:0x2b6653dc', + assetId: 'tron:0x2b6653dc/trc20:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', + symbol: 'USDT', + precision: 6, + name: 'Tether USD', + }, + TON: { + chainId: 'ton:mainnet', + assetId: 'ton:mainnet/slip44:607', + symbol: 'TON', + precision: 9, + name: 'Toncoin', + }, + USDT_TON: { + chainId: 'ton:mainnet', + assetId: 'ton:mainnet/token:EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs', + symbol: 'USDT', + precision: 6, + name: 'Tether USD', + }, + SUI: { + chainId: 'sui:35834a8a', + assetId: 'sui:35834a8a/slip44:784', + symbol: 'SUI', + precision: 9, + name: 'Sui', + }, + USDC_SUI: { + chainId: 'sui:35834a8a', + assetId: 'sui:35834a8a/token:0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC', + symbol: 'USDC', + precision: 6, + name: 'USD Coin', + }, + ETH_ARB: { + chainId: 'eip155:42161', + assetId: 'eip155:42161/slip44:60', + symbol: 'ETH', + precision: 18, + name: 'Ethereum', + }, + USDC_ARB: { + chainId: 'eip155:42161', + assetId: 'eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831', + symbol: 'USDC', + precision: 6, + name: 'USD Coin', + }, +}; + +// ─── Confirmed ETH tx hash (verified via unchained API) ────────────────────── +const CONFIRMED_ETH_TX = '0xd57d57f07e307b562ae2a1b956be571c410fbd61370a9af25fbc36f42252d44d'; + +// ─── Test Case Definitions ─────────────────────────────────────────────────── + +const testCases = [ + // ═══════════════════════════════════════════════════════════════════════════ + // 1. THORChain — Midgard API check (BTC → BNB) + // ═══════════════════════════════════════════════════════════════════════════ + { + key: 'THORChain', + swapperName: 'THORChain', + sellAsset: assets.BTC, + buyAsset: assets.BNB, + sellTxHash: '1B7022CB1DCAE945060875242C77CB030BC7E1665F47C9C150A55516B890BD55', + sellAmountBaseUnit: '265263', + expectedBuyAmountBaseUnit: '28816802', + sellPrecision: '0.00265263', + buyPrecision: '0.028816802', + receiveAddress: RECEIVE_ADDRESSES.ETH, + metadata: {}, + expectResolution: true, + notes: 'Real BTC→BNB swap, verified on Midgard', + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // 2. MAYAChain — Midgard API check (ZEC → BTC) + // ═══════════════════════════════════════════════════════════════════════════ + { + key: 'MAYAChain', + swapperName: 'MAYAChain', + sellAsset: assets.ZEC, + buyAsset: assets.BTC, + sellTxHash: 'C871AC207D81E4C3F83FC8E674E22CED9C3A857ABB146B4BE3DC954813423BED', + sellAmountBaseUnit: '1035839375', + expectedBuyAmountBaseUnit: '3831339', + sellPrecision: '10.35839375', + buyPrecision: '0.03831339', + receiveAddress: RECEIVE_ADDRESSES.BTC, + metadata: {}, + expectResolution: true, + notes: 'Real ZEC→BTC swap, verified on MAYAChain Midgard', + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // 3. CowSwap — CoW Protocol API check (PAPER → WETH) + // ═══════════════════════════════════════════════════════════════════════════ + { + key: 'CowSwap', + swapperName: 'CoW Swap', + sellAsset: assets.PAPER, + buyAsset: assets.WETH, + sellTxHash: '0x68953906f8b73fb07df4b02fc311153a97cb5b7d36117cea9ffc7268db7e4304e75745886f1c28043710fbccb4ae3e25011c6073663b5b13', + sellAmountBaseUnit: '624441726887100729007594', + expectedBuyAmountBaseUnit: '1000000000000000000', + sellPrecision: '624441.726887', + buyPrecision: '1.0', + receiveAddress: RECEIVE_ADDRESSES.ETH, + metadata: {}, + expectResolution: true, + notes: 'Real CowSwap orderUid (sellTxHash IS the orderUid), fulfilled status', + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // 4. 0x (Zrx) — checkEvmSwapStatus (ETH → USDC) + // ═══════════════════════════════════════════════════════════════════════════ + { + key: 'Zrx', + swapperName: '0x', + sellAsset: assets.ETH, + buyAsset: assets.USDC_ETH, + sellTxHash: CONFIRMED_ETH_TX, + sellAmountBaseUnit: '1000000000000000000', + expectedBuyAmountBaseUnit: '2500000000', + sellPrecision: '1.0', + buyPrecision: '2500.0', + receiveAddress: RECEIVE_ADDRESSES.ETH, + metadata: {}, + expectResolution: true, + notes: 'checkEvmSwapStatus — any confirmed ETH tx works', + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // 5. Portals — checkEvmSwapStatus (ETH → USDC) + // ═══════════════════════════════════════════════════════════════════════════ + { + key: 'Portals', + swapperName: 'Portals', + sellAsset: assets.ETH, + buyAsset: assets.USDC_ETH, + sellTxHash: CONFIRMED_ETH_TX, + sellAmountBaseUnit: '500000000000000000', + expectedBuyAmountBaseUnit: '1250000000', + sellPrecision: '0.5', + buyPrecision: '1250.0', + receiveAddress: RECEIVE_ADDRESSES.ETH, + metadata: {}, + expectResolution: true, + notes: 'checkEvmSwapStatus — any confirmed ETH tx works', + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // 6. Bebop — checkEvmSwapStatus (ETH → USDC) + // ═══════════════════════════════════════════════════════════════════════════ + { + key: 'Bebop', + swapperName: 'Bebop', + sellAsset: assets.ETH, + buyAsset: assets.USDC_ETH, + sellTxHash: CONFIRMED_ETH_TX, + sellAmountBaseUnit: '200000000000000000', + expectedBuyAmountBaseUnit: '500000000', + sellPrecision: '0.2', + buyPrecision: '500.0', + receiveAddress: RECEIVE_ADDRESSES.ETH, + metadata: {}, + expectResolution: true, + notes: 'checkEvmSwapStatus — any confirmed ETH tx works', + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // 7. Jupiter — Solana on-chain check (SOL → USDC) + // ═══════════════════════════════════════════════════════════════════════════ + { + key: 'Jupiter', + swapperName: 'Jupiter', + sellAsset: assets.SOL, + buyAsset: assets.USDC_SOL, + sellTxHash: 'pgu7kyvPSaVjx7gCnBSy8ZujdcdpMTJRw3Cj7CGyMvzaA9NnYzNhnB5U7Avvo2Yd4JdMBBuP23ZFPqCRQNZa66b', + sellAmountBaseUnit: '1000000000', + expectedBuyAmountBaseUnit: '150000000', + sellPrecision: '1.0', + buyPrecision: '150.0', + receiveAddress: RECEIVE_ADDRESSES.SOL, + metadata: {}, + expectResolution: true, + notes: 'Real Solana tx signature, on-chain confirmation check', + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // 8. AVNU — Starknet on-chain check (STRK → USDC) + // ═══════════════════════════════════════════════════════════════════════════ + { + key: 'AVNU', + swapperName: 'AVNU', + sellAsset: assets.STRK, + buyAsset: assets.USDC_STRK, + sellTxHash: '0x7ce366ad1caf16cf73b347bd934802badba4be802db56d249347be6b52a0769', + sellAmountBaseUnit: '10000000000000000000', + expectedBuyAmountBaseUnit: '5000000', + sellPrecision: '10.0', + buyPrecision: '5.0', + receiveAddress: RECEIVE_ADDRESSES.STRK, + metadata: {}, + expectResolution: true, + notes: 'Real Starknet tx, verified AVNU STRK→USDC swap', + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // 9. Sun.io — Tron on-chain check (TRX → USDT) + // ═══════════════════════════════════════════════════════════════════════════ + { + key: 'Sunio', + swapperName: 'Sun.io', + sellAsset: assets.TRX, + buyAsset: assets.USDT_TRX, + sellTxHash: '611bcea1373817f762cc704eb183eb12319ef2b99c02baf392755bf58d2bbc26', + sellAmountBaseUnit: '100000000', + expectedBuyAmountBaseUnit: '25000000', + sellPrecision: '100.0', + buyPrecision: '25.0', + receiveAddress: RECEIVE_ADDRESSES.TRX, + metadata: {}, + expectResolution: true, + notes: 'Real Tron tx, on-chain confirmation check', + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // 10. STON.fi — TON chain adapter fallback (TON → USDT) + // ═══════════════════════════════════════════════════════════════════════════ + { + key: 'Stonfi', + swapperName: 'STON.fi', + sellAsset: assets.TON, + buyAsset: assets.USDT_TON, + sellTxHash: 'fe23a32504db377ad8c40a41cf1d4bba072303fe2e645e80efbc7ea166e17728', + sellAmountBaseUnit: '3899999000', + expectedBuyAmountBaseUnit: '38768000', + sellPrecision: '3.899999', + buyPrecision: '38.768', + receiveAddress: RECEIVE_ADDRESSES.TON, + metadata: {}, // no quoteId → falls back to checkTxStatusViaChainAdapter + expectResolution: false, // TON adapter parseTx needs sender address, which is not available in test + notes: 'TON chain adapter needs sender address for tx lookup (API limitation)', + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // 11. Cetus — Sui on-chain check (SUI → USDC) + // ═══════════════════════════════════════════════════════════════════════════ + { + key: 'Cetus', + swapperName: 'Cetus', + sellAsset: assets.SUI, + buyAsset: assets.USDC_SUI, + sellTxHash: 'D53Mcc8adGYVawgtQUEktRW4fv4dfUHbPQcEC3p1qALp', + sellAmountBaseUnit: '1000000000', + expectedBuyAmountBaseUnit: '1500000', + sellPrecision: '1.0', + buyPrecision: '1.5', + receiveAddress: RECEIVE_ADDRESSES.SUI, + metadata: {}, + expectResolution: true, + notes: 'Confirmed Sui tx digest from latest checkpoint', + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // 12. Relay — Relay API + EVM check (ETH → ETH-ARB) + // ═══════════════════════════════════════════════════════════════════════════ + { + key: 'Relay', + swapperName: 'Relay', + sellAsset: assets.ETH, + buyAsset: assets.ETH_ARB, + sellTxHash: CONFIRMED_ETH_TX, + sellAmountBaseUnit: '100000000000000000', + expectedBuyAmountBaseUnit: '99500000000000000', + sellPrecision: '0.1', + buyPrecision: '0.0995', + receiveAddress: RECEIVE_ADDRESSES.ARB, + metadata: { + relayTransactionMetadata: { + relayId: '0xbc19c76d9658db7e640d9d5a387f116b9d992e9bc730ab26b590f7a9fbb2b933', + }, + }, + expectResolution: true, + notes: 'Real Relay requestId, EVM tx confirmed + Relay API status check', + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // 13. Across — EVM + Across deposit status API (ETH USDC → ARB USDC) + // ═══════════════════════════════════════════════════════════════════════════ + { + key: 'Across', + swapperName: 'Across', + sellAsset: assets.USDC_ETH, + buyAsset: assets.USDC_ARB, + sellTxHash: CONFIRMED_ETH_TX, + sellAmountBaseUnit: '1000000000', + expectedBuyAmountBaseUnit: '999000000', + sellPrecision: '1000.0', + buyPrecision: '999.0', + receiveAddress: RECEIVE_ADDRESSES.ARB, + metadata: {}, + expectResolution: false, // EVM part confirms, but Across deposit API won't recognize this tx + notes: 'EVM tx confirmed, but Across deposit status API will return unknown (not a real deposit)', + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // SKIPPED SWAPPERS — no valid test data available + // ═══════════════════════════════════════════════════════════════════════════ + // ═══════════════════════════════════════════════════════════════════════════ + // 14. Chainflip — broker API check (needs real swapId from this broker) + // ═══════════════════════════════════════════════════════════════════════════ + { + key: 'Chainflip', + swapperName: 'Chainflip', + sellAsset: assets.ETH, + buyAsset: assets.BTC, + sellTxHash: CONFIRMED_ETH_TX, // Needs a sellTxHash for polling to pick it up + sellAmountBaseUnit: '1000000000000000000', + expectedBuyAmountBaseUnit: '4000000', + sellPrecision: '1.0', + buyPrecision: '0.04', + receiveAddress: RECEIVE_ADDRESSES.BTC, + metadata: { chainflipSwapId: 999999 }, // Fake ID — broker will return 404 → Unknown → PENDING + expectResolution: false, + notes: 'Broker returns 404 for unknown swapId → stays PENDING (graceful). Need real swap through this broker to resolve.', + }, + // ═══════════════════════════════════════════════════════════════════════════ + // 15. ArbitrumBridge — L2→L1 withdraw (confirmed Arbitrum tx → instant SUCCESS) + // ═══════════════════════════════════════════════════════════════════════════ + { + key: 'ArbitrumBridge', + swapperName: 'Arbitrum Bridge', + sellAsset: assets.ETH_ARB, // Sell from Arbitrum = L2→L1 withdraw path + buyAsset: assets.ETH, // Buy on Ethereum + sellTxHash: '0x9a58d12a751803824d16e326d770aca4c715997d7894cf6a85ee766e576d114c', // Confirmed Arbitrum tx + sellAmountBaseUnit: '100000000000000000', + expectedBuyAmountBaseUnit: '100000000000000000', + sellPrecision: '0.1', + buyPrecision: '0.1', + receiveAddress: RECEIVE_ADDRESSES.ETH, + metadata: {}, + expectResolution: true, + notes: 'L2→L1 withdraw returns SUCCESS immediately on Arbitrum tx confirmation (no 7-day wait in status check)', + }, + // ═══════════════════════════════════════════════════════════════════════════ + // 16. ButterSwap — same-chain EVM swap (falls through to checkEvmSwapStatus) + // ═══════════════════════════════════════════════════════════════════════════ + { + key: 'ButterSwap', + swapperName: 'ButterSwap', + sellAsset: assets.ETH, // Same chain sell/buy = same-chain swap path + buyAsset: assets.USDC_ETH, // Same chain = no bridge indexer needed + sellTxHash: CONFIRMED_ETH_TX, + sellAmountBaseUnit: '100000000000000000', + expectedBuyAmountBaseUnit: '250000000', + sellPrecision: '0.1', + buyPrecision: '250.0', + receiveAddress: RECEIVE_ADDRESSES.ETH, + metadata: {}, + expectResolution: true, + notes: 'Same-chain EVM swap — checkEvmSwapStatus only, no bridge indexer needed', + }, + // ═══════════════════════════════════════════════════════════════════════════ + // 17. NearIntents — 1Click API status check (BTC → ZEC via NEAR Intents) + // ═══════════════════════════════════════════════════════════════════════════ + { + key: 'NearIntents', + swapperName: 'NEAR Intents', + sellAsset: assets.BTC, + buyAsset: assets.ZEC, + sellTxHash: 'd8522069088bc9c4c4566250f43dad0734b995b5ab7570c18c493bba38a359b9', // Origin chain tx hash from 1Click API + sellAmountBaseUnit: '800000000', + expectedBuyAmountBaseUnit: '211248545378', + sellPrecision: '8.0', + buyPrecision: '2112.48545378', + receiveAddress: RECEIVE_ADDRESSES.ZEC, + metadata: { + nearIntentsSpecific: { + depositAddress: '1Q7cJr15wiLtScN8npqQf3rcg3zgfY9dck', // Real completed BTC→ZEC deposit + }, + }, + expectResolution: true, + notes: 'Real 1Click deposit address, verified SUCCESS via https://1click.chaindefuser.com/v0/status', + }, +]; + +// ─── Main Test Runner ──────────────────────────────────────────────────────── + +async function main() { + console.log('\n' + '═'.repeat(80)); + console.log(' COMPREHENSIVE SWAPPER TEST — ALL 18 SWAPPERS'); + console.log(' ' + new Date().toISOString()); + console.log('═'.repeat(80) + '\n'); + + // Health check + try { + const healthRes = await fetch(`${SWAP_SERVICE_URL}/swaps/pending`); + if (!healthRes.ok) throw new Error(`Status ${healthRes.status}`); + console.log('✓ Swap service healthy at', SWAP_SERVICE_URL); + } catch (err) { + console.error('✗ Swap service not reachable:', err.message); + process.exit(1); + } + + // Cleanup old test swaps so polling queue is clean + try { + const cleanRes = await fetch(`${SWAP_SERVICE_URL}/swaps/test-cleanup`, { method: 'DELETE' }); + if (cleanRes.ok) { + const { cleaned } = await cleanRes.json(); + if (cleaned > 0) console.log(`✓ Cleaned ${cleaned} old test swap(s) from polling queue`); + } + } catch { + // Non-fatal — just means old swaps may slow polling + } + console.log(''); + + const results = []; + const testableCount = testCases.filter((tc) => tc.sellTxHash !== null).length; + const skippedCount = testCases.filter((tc) => tc.sellTxHash === null).length; + + console.log(`Testing ${testableCount} swappers, skipping ${skippedCount} (no test data)\n`); + + // ── Phase 1: Create all swaps ───────────────────────────────────────────── + console.log('─── PHASE 1: Creating test swaps ───────────────────────────────\n'); + + const createdSwaps = []; + + for (const tc of testCases) { + const swapId = makeSwapId(tc.key); + + if (tc.sellTxHash === null) { + console.log(` ⊘ ${tc.swapperName.padEnd(18)} SKIPPED — ${tc.notes}`); + results.push({ + swapper: tc.swapperName, + key: tc.key, + status: 'SKIPPED', + reason: tc.notes, + }); + continue; + } + + try { + const payload = { + swapId, + sellAsset: tc.sellAsset, + buyAsset: tc.buyAsset, + sellTxHash: tc.sellTxHash, + sellAmountCryptoBaseUnit: tc.sellAmountBaseUnit, + expectedBuyAmountCryptoBaseUnit: tc.expectedBuyAmountBaseUnit, + sellAmountCryptoPrecision: tc.sellPrecision, + expectedBuyAmountCryptoPrecision: tc.buyPrecision, + source: 'test-script', + swapperName: tc.swapperName, + sellAccountId: `${tc.sellAsset.chainId}:0xTestAccount:0x0000000000000000000000000000000000000000`, + receiveAddress: tc.receiveAddress || null, + metadata: tc.metadata, + affiliateAddress: AFFILIATE_ADDRESS, + affiliateBps: AFFILIATE_BPS, + origin: 'web', + }; + + await createSwap(payload); + console.log(` ✓ ${tc.swapperName.padEnd(18)} created → ${swapId}`); + createdSwaps.push({ ...tc, swapId }); + } catch (err) { + console.log(` ✗ ${tc.swapperName.padEnd(18)} FAILED to create: ${err.message}`); + results.push({ + swapper: tc.swapperName, + key: tc.key, + status: 'CREATE_FAILED', + reason: err.message, + }); + } + } + + console.log(`\nCreated ${createdSwaps.length} swaps. Waiting for polling to resolve...\n`); + + // ── Phase 2: Poll ALL swaps in parallel ───────────────────────────────── + console.log('─── PHASE 2: Waiting for resolution (max 2 min, all in parallel) ─\n'); + + // Track which swaps are still pending + const pending = new Map(createdSwaps.map((tc) => [tc.swapId, tc])); + const resolved = new Map(); // swapId → { tc, finalSwap } + + for (let attempt = 0; attempt < MAX_POLL_ATTEMPTS && pending.size > 0; attempt++) { + await sleep(POLL_INTERVAL_MS); + + // Poll all remaining pending swaps in parallel + const checks = await Promise.all( + [...pending.entries()].map(async ([swapId, tc]) => { + const swap = await getSwap(swapId); + return { swapId, tc, swap }; + }), + ); + + for (const { swapId, tc, swap } of checks) { + if (!swap) continue; + if (swap.status === 'SUCCESS' || swap.status === 'FAILED') { + pending.delete(swapId); + resolved.set(swapId, { tc, finalSwap: swap }); + } + } + + const resolvedCount = createdSwaps.length - pending.size; + process.stdout.write(`\r Poll ${attempt + 1}/${MAX_POLL_ATTEMPTS}: ${resolvedCount}/${createdSwaps.length} resolved`); + } + + console.log('\n'); + + // Build results from resolved + still-pending + for (const tc of createdSwaps) { + const entry = resolved.get(tc.swapId); + + if (entry) { + const { finalSwap } = entry; + if (finalSwap.status === 'SUCCESS') { + const affiliateOk = finalSwap.isAffiliateVerified === true; + const icon = affiliateOk ? '✓' : '⚠'; + console.log( + ` ${tc.swapperName.padEnd(18)} → SUCCESS ${icon} affiliate=${affiliateOk ? 'VERIFIED' : 'NOT_VERIFIED'}` + + (finalSwap.affiliateVerificationDetails + ? ` bps=${finalSwap.affiliateVerificationDetails.affiliateBps ?? '?'}` + : ''), + ); + results.push({ + swapper: tc.swapperName, + key: tc.key, + status: 'SUCCESS', + affiliateVerified: affiliateOk, + affiliateDetails: finalSwap.affiliateVerificationDetails, + buyTxHash: finalSwap.buyTxHash, + }); + } else { + console.log(` ${tc.swapperName.padEnd(18)} → FAILED: ${finalSwap.statusMessage || 'unknown'}`); + results.push({ + swapper: tc.swapperName, + key: tc.key, + status: 'FAILED', + statusMessage: finalSwap.statusMessage, + }); + } + } else { + // Still pending after all attempts + const finalSwap = await getSwap(tc.swapId); + const currentStatus = finalSwap ? finalSwap.status : 'UNKNOWN'; + const expected = tc.expectResolution ? 'UNEXPECTED' : 'EXPECTED'; + console.log(` ${tc.swapperName.padEnd(18)} → STILL ${currentStatus} (${expected} — ${tc.notes})`); + results.push({ + swapper: tc.swapperName, + key: tc.key, + status: `STUCK_${currentStatus}`, + expected: tc.expectResolution ? 'SHOULD_RESOLVE' : 'EXPECTED_STUCK', + notes: tc.notes, + }); + } + } + + // ── Phase 3: Summary ────────────────────────────────────────────────────── + console.log('\n' + '═'.repeat(80)); + console.log(' RESULTS SUMMARY'); + console.log('═'.repeat(80) + '\n'); + + const success = results.filter((r) => r.status === 'SUCCESS'); + const successVerified = success.filter((r) => r.affiliateVerified); + const failed = results.filter((r) => r.status === 'FAILED'); + const stuck = results.filter((r) => r.status.startsWith('STUCK_')); + const stuckExpected = stuck.filter((r) => r.expected === 'EXPECTED_STUCK'); + const stuckUnexpected = stuck.filter((r) => r.expected === 'SHOULD_RESOLVE'); + const skipped = results.filter((r) => r.status === 'SKIPPED'); + const createFailed = results.filter((r) => r.status === 'CREATE_FAILED'); + + console.log(` ✓ SUCCESS: ${success.length} (${successVerified.length} affiliate verified)`); + console.log(` ✗ FAILED: ${failed.length}`); + console.log(` ◌ STUCK (expected): ${stuckExpected.length}`); + console.log(` ⚠ STUCK (unexpected): ${stuckUnexpected.length}`); + console.log(` ⊘ SKIPPED: ${skipped.length}`); + console.log(` ✗ CREATE FAILED: ${createFailed.length}`); + console.log(` ─────────────────────────`); + console.log(` TOTAL: ${results.length}/18\n`); + + // Detailed table + console.log(' Swapper Status Affiliate Notes'); + console.log(' ' + '─'.repeat(76)); + + for (const r of results) { + const swapper = r.swapper.padEnd(20); + const status = r.status.padEnd(15); + let affiliate = ''; + let notes = ''; + + if (r.status === 'SUCCESS') { + affiliate = r.affiliateVerified ? 'VERIFIED' : 'NOT_VERIFIED'; + if (r.affiliateDetails) { + notes = `bps=${r.affiliateDetails.affiliateBps ?? '?'}`; + } + } else if (r.status === 'SKIPPED') { + notes = r.reason; + } else if (r.status === 'FAILED') { + notes = r.statusMessage || ''; + } else if (r.status.startsWith('STUCK_')) { + notes = r.notes || ''; + } else if (r.status === 'CREATE_FAILED') { + notes = r.reason || ''; + } + + console.log(` ${swapper} ${status} ${affiliate.padEnd(12)} ${notes}`); + } + + console.log('\n' + '═'.repeat(80) + '\n'); + + // Exit code + if (stuckUnexpected.length > 0 || failed.length > 0 || createFailed.length > 0) { + console.log('⚠ Some tests had issues. Check details above.\n'); + process.exit(1); + } else { + console.log('✓ All testable swappers behaved as expected.\n'); + process.exit(0); + } +} + +main().catch((err) => { + console.error('Fatal error:', err); + process.exit(1); +}); diff --git a/yarn.lock b/yarn.lock index c936177..40956c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -37,7 +37,7 @@ __metadata: languageName: node linkType: hard -"@adraffy/ens-normalize@npm:^1.11.0": +"@adraffy/ens-normalize@npm:^1.10.1, @adraffy/ens-normalize@npm:^1.11.0": version: 1.11.1 resolution: "@adraffy/ens-normalize@npm:1.11.1" checksum: 10/dd19274d9fcaf99bf08a62b64e54f4748de11b235767addbd3f7385ae1b7777bd704d17ff003ffaa3295a0b9d035929381cf3b38329c96260bff96aab8ad7b37 @@ -144,6 +144,21 @@ __metadata: languageName: node linkType: hard +"@avnu/avnu-sdk@npm:^4.0.1": + version: 4.0.1 + resolution: "@avnu/avnu-sdk@npm:4.0.1" + dependencies: + dayjs: "npm:^1.11.19" + moment: "npm:^2.30.1" + qs: "npm:^6.14.0" + zod: "npm:^4.2.1" + peerDependencies: + ethers: ^6.15.0 + starknet: ^8.9.1 + checksum: 10/8074d9b38b92e98d603929eae19dc627f72dfa3116b49ba179691b47fc57b4576bafa146f91f6a8bb8ed855e5a2f5724b213c4ae771f60ac736c55b1ee168e72 + languageName: node + linkType: hard + "@aws-crypto/sha256-browser@npm:5.2.0": version: 5.2.0 resolution: "@aws-crypto/sha256-browser@npm:5.2.0" @@ -1052,6 +1067,22 @@ __metadata: languageName: node linkType: hard +"@cetusprotocol/aggregator-sdk@npm:^1.4.5": + version: 1.4.5 + resolution: "@cetusprotocol/aggregator-sdk@npm:1.4.5" + dependencies: + "@mysten/sui": "npm:^1.0.5" + "@pythnetwork/pyth-sui-js": "npm:^2.1.0" + bip39: "npm:^3.1.0" + dotenv: "npm:^16.4.5" + json-bigint: "npm:^1.0.0" + node-fetch: "npm:^3.3.2" + peerDependencies: + typescript: ^5.0.0 + checksum: 10/1663b1da0c13051a33ad3e7236cc333d0eb38771977b0a1a7b0ca247fe205a38d1212dd87294f954b4446b3a7fa92e60eba25d243176e45682e8d24be007de93 + languageName: node + linkType: hard + "@colors/colors@npm:1.5.0": version: 1.5.0 resolution: "@colors/colors@npm:1.5.0" @@ -3089,95 +3120,102 @@ __metadata: languageName: node linkType: hard -"@ledgerhq/cryptoassets-evm-signatures@npm:^13.7.1": - version: 13.7.1 - resolution: "@ledgerhq/cryptoassets-evm-signatures@npm:13.7.1" +"@ledgerhq/client-ids@npm:0.5.1": + version: 0.5.1 + resolution: "@ledgerhq/client-ids@npm:0.5.1" dependencies: - "@ledgerhq/live-env": "npm:^2.21.0" - axios: "npm:1.12.2" - checksum: 10/0da65f49685eef9ee7cf44ac7dd81439cef2c09b78befa7600617191fdff6bc533a147e32431bb3cac7afe9aab1684a3d88b6392415bb385ff3b770a2f06b863 + "@ledgerhq/live-env": "npm:^2.27.0" + "@reduxjs/toolkit": "npm:2.11.2" + uuid: "npm:^9.0.0" + checksum: 10/2fb18b9ac7bee3b17a8a7f24baad4569698dcefbc3b519d653619f9082f01694896b119be3cf1d90fd2c5ded7e866cf3f9ef75f57adf6d650163d89623d46667 languageName: node linkType: hard -"@ledgerhq/device-core@npm:^0.5.4": - version: 0.5.5 - resolution: "@ledgerhq/device-core@npm:0.5.5" +"@ledgerhq/device-core@npm:0.6.9": + version: 0.6.9 + resolution: "@ledgerhq/device-core@npm:0.6.9" dependencies: - "@ledgerhq/devices": "npm:8.4.8" - "@ledgerhq/errors": "npm:^6.23.0" - "@ledgerhq/hw-transport": "npm:^6.31.8" - "@ledgerhq/live-network": "npm:^2.0.14" + "@ledgerhq/devices": "npm:8.7.0" + "@ledgerhq/errors": "npm:^6.27.0" + "@ledgerhq/hw-transport": "npm:6.31.13" + "@ledgerhq/live-network": "npm:^2.1.2" "@ledgerhq/logs": "npm:^6.13.0" - "@ledgerhq/types-live": "npm:^6.80.0" + "@ledgerhq/types-live": "npm:^6.90.0" + "@noble/hashes": "npm:1.8.0" semver: "npm:^7.3.5" - sha.js: "npm:^2.4.11" - checksum: 10/858b6ecdab4bd3d8f90e8340b188d44e7c399de6187fb05460ace2a04bf6f2ab76a2c5b1d95f277eca185dd22a45edfbb4d06f8c5ef0c7ecec669f6caa30f7cb + checksum: 10/b6f791e35dae6c63eaea03f64009977c001b3eb3bc7092b1c3c20edcd2ebf5f87ee35e909f72030e123dcf55269f76b3d4d4bd6b7705b7c8e0e19b12212f54a6 languageName: node linkType: hard -"@ledgerhq/devices@npm:8.4.8": - version: 8.4.8 - resolution: "@ledgerhq/devices@npm:8.4.8" +"@ledgerhq/devices@npm:8.7.0, @ledgerhq/devices@npm:^8.6.1": + version: 8.7.0 + resolution: "@ledgerhq/devices@npm:8.7.0" dependencies: - "@ledgerhq/errors": "npm:^6.23.0" + "@ledgerhq/errors": "npm:^6.27.0" "@ledgerhq/logs": "npm:^6.13.0" rxjs: "npm:^7.8.1" semver: "npm:^7.3.5" - checksum: 10/4ce35892708d6f4e4488f166fbf8f71caad22867a563ab230391922394b84ce7e45eaae575e5a71174a4b260871acad0f72cd521e4987c1bbcc993fb9cee9487 + checksum: 10/0823bebcde4ea6c6f59abe977e110d669f23abde9b05b19fc18555c85fc49a7d14940b1f17c4d4c1fa11bb74e59ec8390348ea8561c4defa6c79d082ec2f6166 languageName: node linkType: hard -"@ledgerhq/devices@npm:8.7.0, @ledgerhq/devices@npm:^8.6.1": - version: 8.7.0 - resolution: "@ledgerhq/devices@npm:8.7.0" +"@ledgerhq/devices@npm:8.7.1": + version: 8.7.1 + resolution: "@ledgerhq/devices@npm:8.7.1" dependencies: - "@ledgerhq/errors": "npm:^6.27.0" + "@ledgerhq/errors": "npm:^6.27.1" "@ledgerhq/logs": "npm:^6.13.0" rxjs: "npm:^7.8.1" semver: "npm:^7.3.5" - checksum: 10/0823bebcde4ea6c6f59abe977e110d669f23abde9b05b19fc18555c85fc49a7d14940b1f17c4d4c1fa11bb74e59ec8390348ea8561c4defa6c79d082ec2f6166 + checksum: 10/14bee2d8dfd9964a9c90ad65660348614d384493c2350c26ecd0a1f5cb9b21aae4d3955d2f064066aab75f1230014e0fb63b8fae85e33be6c46fd1fe8576c560 languageName: node linkType: hard -"@ledgerhq/domain-service@npm:^1.4.1": - version: 1.4.1 - resolution: "@ledgerhq/domain-service@npm:1.4.1" +"@ledgerhq/domain-service@npm:^1.4.2": + version: 1.6.4 + resolution: "@ledgerhq/domain-service@npm:1.6.4" dependencies: - "@ledgerhq/errors": "npm:^6.27.0" - "@ledgerhq/logs": "npm:^6.13.0" - "@ledgerhq/types-live": "npm:^6.89.0" - axios: "npm:1.12.2" + "@ledgerhq/errors": "npm:^6.29.0" + "@ledgerhq/logs": "npm:^6.14.0" + "@ledgerhq/types-live": "npm:^6.97.0" + axios: "npm:1.13.2" eip55: "npm:^2.1.1" react: "npm:18.3.1" react-dom: "npm:18.3.1" - checksum: 10/e94c9e481cb3309b77f88bd8f00a3b702baf4834debb7afa41fe4e27a4d2b3897185cf6597602a239a6eb460a6aac595b2d75932a6df7538b0d55bad30bf043e + checksum: 10/f8a94f730ede446c59a46c41d311c3549de397c003f4465dc82ed282a0ff3a483520ee0152eaff10f3d1c84ab42251ace83c0a9d299d9cd42a934acd65d860e2 languageName: node linkType: hard -"@ledgerhq/errors@npm:^6.23.0, @ledgerhq/errors@npm:^6.26.0, @ledgerhq/errors@npm:^6.27.0": +"@ledgerhq/errors@npm:^6.26.0, @ledgerhq/errors@npm:^6.27.0": version: 6.27.0 resolution: "@ledgerhq/errors@npm:6.27.0" checksum: 10/ea424e032fa93ca15536f225a068f80129acca7b36d039cf98925bdb33f178d935a1ada4ebe54d79951dbce2405dc7094cdc7d05c231186fd6b2de7993ad7fdc languageName: node linkType: hard -"@ledgerhq/evm-tools@npm:^1.8.1": - version: 1.8.1 - resolution: "@ledgerhq/evm-tools@npm:1.8.1" +"@ledgerhq/errors@npm:^6.27.1, @ledgerhq/errors@npm:^6.29.0": + version: 6.29.0 + resolution: "@ledgerhq/errors@npm:6.29.0" + checksum: 10/0c82f1d50ceb474f9ca158c6709c77a0ecc4e8ef2eaedc76f128a55ad38967afcb9613ca6a54c805fdb78615c1ddea027a30802569e67d162dc57562b1cdf11e + languageName: node + linkType: hard + +"@ledgerhq/evm-tools@npm:^1.8.2": + version: 1.11.0 + resolution: "@ledgerhq/evm-tools@npm:1.11.0" dependencies: "@ethersproject/constants": "npm:^5.7.0" "@ethersproject/hash": "npm:^5.7.0" - "@ledgerhq/cryptoassets-evm-signatures": "npm:^13.7.1" - "@ledgerhq/live-env": "npm:^2.21.0" - axios: "npm:1.12.2" + "@ledgerhq/live-env": "npm:^2.27.0" + axios: "npm:1.13.2" crypto-js: "npm:4.2.0" - checksum: 10/15f53bba985050af244eed6af04de4083d9aa9e1005a6ffa3a69bda80c7b7e150d2b710b50042f073d2e5a3750e7d5269589b35a7d0608318f2a38fd48dafb6b + checksum: 10/669cb364b704e239cb43035487fde9fe79858fbea7f68316495c2f4f29a145195b92791c6f95fc985c584145ebe82e0fd2fb4760da53640524d55a8129b69b91 languageName: node linkType: hard -"@ledgerhq/hw-app-btc@npm:^10.10.0": - version: 10.12.0 - resolution: "@ledgerhq/hw-app-btc@npm:10.12.0" +"@ledgerhq/hw-app-btc@npm:10.13.0": + version: 10.13.0 + resolution: "@ledgerhq/hw-app-btc@npm:10.13.0" dependencies: "@ledgerhq/hw-transport": "npm:6.31.13" "@ledgerhq/logs": "npm:^6.13.0" @@ -3190,11 +3228,11 @@ __metadata: invariant: "npm:^2.2.4" semver: "npm:^7.3.5" varuint-bitcoin: "npm:1.1.2" - checksum: 10/feb3da33ea012bebc0398bbcb602bcd1c4f64ea7561d4276016a956e3d279ef1989f35d2199d842b27eedd8380d17550afd63bf38d2e9a6085a398f7b501eea9 + checksum: 10/5cb0718bb0c2d15e4e0822bc45eacbacc0061e88598f4b2d7b77943679923c1c7ef27da03c047385750ce8d72e15d45bf5d97ab07d06bfdc6465a9c08cbb8ef2 languageName: node linkType: hard -"@ledgerhq/hw-app-cosmos@npm:^6.32.4": +"@ledgerhq/hw-app-cosmos@npm:6.32.9": version: 6.32.9 resolution: "@ledgerhq/hw-app-cosmos@npm:6.32.9" dependencies: @@ -3205,29 +3243,38 @@ __metadata: languageName: node linkType: hard -"@ledgerhq/hw-app-eth@npm:^6.45.13": - version: 6.47.1 - resolution: "@ledgerhq/hw-app-eth@npm:6.47.1" +"@ledgerhq/hw-app-eth@npm:7.0.0": + version: 7.0.0 + resolution: "@ledgerhq/hw-app-eth@npm:7.0.0" dependencies: "@ethersproject/abi": "npm:^5.7.0" "@ethersproject/rlp": "npm:^5.7.0" "@ethersproject/transactions": "npm:^5.7.0" - "@ledgerhq/cryptoassets-evm-signatures": "npm:^13.7.1" - "@ledgerhq/domain-service": "npm:^1.4.1" + "@ledgerhq/domain-service": "npm:^1.4.2" "@ledgerhq/errors": "npm:^6.27.0" - "@ledgerhq/evm-tools": "npm:^1.8.1" + "@ledgerhq/evm-tools": "npm:^1.8.2" "@ledgerhq/hw-transport": "npm:6.31.13" "@ledgerhq/hw-transport-mocker": "npm:^6.29.13" "@ledgerhq/logs": "npm:^6.13.0" - "@ledgerhq/types-live": "npm:^6.89.0" + "@ledgerhq/types-live": "npm:^6.90.0" axios: "npm:1.12.2" bignumber.js: "npm:^9.1.2" semver: "npm:^7.3.5" - checksum: 10/a71ed5444952f35174f00c822b3f1b638ecc8ab386742291a1c16002d52a78bf977573802a6e2f6a0e3b4d000e9a653c3eaf73db5e81db11766f68037360a85d + checksum: 10/7c07e69de5c5518f7557e809ac334384dea65a6ff82245a7810684e65d5dd06e2d5d93f96a7feb8c888c974e8398a1cb98c9cf7607de17ab43592451905acebc + languageName: node + linkType: hard + +"@ledgerhq/hw-app-near@npm:6.31.10": + version: 6.31.10 + resolution: "@ledgerhq/hw-app-near@npm:6.31.10" + dependencies: + "@ledgerhq/hw-transport": "npm:6.31.14" + near-api-js: "npm:^3.0.2" + checksum: 10/370a66200fa8b3ec68f923b25096c64146d8e10b1d65dc471ac5345f1ee1dadbd64af96d631ca121e0789c56880f564a3174f88e68c3b2a4a407ac5b507dfa99 languageName: node linkType: hard -"@ledgerhq/hw-app-solana@npm:^7.5.1": +"@ledgerhq/hw-app-solana@npm:7.6.0": version: 7.6.0 resolution: "@ledgerhq/hw-app-solana@npm:7.6.0" dependencies: @@ -3238,7 +3285,7 @@ __metadata: languageName: node linkType: hard -"@ledgerhq/hw-app-trx@npm:^6.31.9": +"@ledgerhq/hw-app-trx@npm:6.31.9": version: 6.31.9 resolution: "@ledgerhq/hw-app-trx@npm:6.31.9" dependencies: @@ -3267,7 +3314,7 @@ __metadata: languageName: node linkType: hard -"@ledgerhq/hw-transport@npm:6.31.13, @ledgerhq/hw-transport@npm:^6.31.10, @ledgerhq/hw-transport@npm:^6.31.8": +"@ledgerhq/hw-transport@npm:6.31.13, @ledgerhq/hw-transport@npm:^6.31.10": version: 6.31.13 resolution: "@ledgerhq/hw-transport@npm:6.31.13" dependencies: @@ -3279,15 +3326,15 @@ __metadata: languageName: node linkType: hard -"@ledgerhq/hw-transport@npm:6.31.8": - version: 6.31.8 - resolution: "@ledgerhq/hw-transport@npm:6.31.8" +"@ledgerhq/hw-transport@npm:6.31.14": + version: 6.31.14 + resolution: "@ledgerhq/hw-transport@npm:6.31.14" dependencies: - "@ledgerhq/devices": "npm:8.4.8" - "@ledgerhq/errors": "npm:^6.23.0" + "@ledgerhq/devices": "npm:8.7.1" + "@ledgerhq/errors": "npm:^6.27.1" "@ledgerhq/logs": "npm:^6.13.0" events: "npm:^3.3.0" - checksum: 10/f5a639baa0e2dfa22a3e82695398ba7e16b4b21f73b1140f7a152433d8665955f193034bb8a58d929d06f0b81bdb2cd5c745d11cc1e8dc395d1ac558acb5327a + checksum: 10/4434b2d288e66aeaa044aeb5ed2279a0518498aff099a6dfd089e9f8a097ceda24df6c17681a19f040b76f598e29c2c6fb30e8206a99fb71edc3046518e550a7 languageName: node linkType: hard @@ -3331,6 +3378,16 @@ __metadata: languageName: node linkType: hard +"@ledgerhq/live-env@npm:^2.27.0": + version: 2.27.0 + resolution: "@ledgerhq/live-env@npm:2.27.0" + dependencies: + rxjs: "npm:7.8.2" + utility-types: "npm:^3.10.0" + checksum: 10/396410a29f1e0e5e227f731b336a1109b90999bb3ad8c6841e23450a0e350ccf438be46293ebfa6d3cfb2c1d7b5039d07e9caed8b8bd1825f27664a91e8b8d46 + languageName: node + linkType: hard + "@ledgerhq/live-network@npm:2.0.19": version: 2.0.19 resolution: "@ledgerhq/live-network@npm:2.0.19" @@ -3345,7 +3402,7 @@ __metadata: languageName: node linkType: hard -"@ledgerhq/live-network@npm:2.1.1, @ledgerhq/live-network@npm:^2.0.14": +"@ledgerhq/live-network@npm:2.1.1": version: 2.1.1 resolution: "@ledgerhq/live-network@npm:2.1.1" dependencies: @@ -3359,6 +3416,20 @@ __metadata: languageName: node linkType: hard +"@ledgerhq/live-network@npm:^2.1.2": + version: 2.2.2 + resolution: "@ledgerhq/live-network@npm:2.2.2" + dependencies: + "@ledgerhq/errors": "npm:^6.29.0" + "@ledgerhq/live-env": "npm:^2.27.0" + "@ledgerhq/live-promise": "npm:^0.2.0" + "@ledgerhq/logs": "npm:^6.14.0" + axios: "npm:1.13.2" + lru-cache: "npm:^7.14.1" + checksum: 10/8b6d64e30fbb3cd342e1dfd9a0bf41250d9a6cf5473fd6c57d238661b97ef580b1aee41a00290638d7fdddcfdb256546186a66907842a3bdd7991fb3f94f642d + languageName: node + linkType: hard + "@ledgerhq/live-promise@npm:^0.1.1": version: 0.1.1 resolution: "@ledgerhq/live-promise@npm:0.1.1" @@ -3368,6 +3439,15 @@ __metadata: languageName: node linkType: hard +"@ledgerhq/live-promise@npm:^0.2.0": + version: 0.2.0 + resolution: "@ledgerhq/live-promise@npm:0.2.0" + dependencies: + "@ledgerhq/logs": "npm:^6.14.0" + checksum: 10/5fb6480ee1837cbe502c694777f1e5160c1b6a49f4e6eebf9ec371df84b4cec6a62be2478cb055c5cdd14a8fe9bbc8a9574948f70fc5728b454cf71c2d099a3d + languageName: node + linkType: hard + "@ledgerhq/logs@npm:6.13.0, @ledgerhq/logs@npm:^6.13.0": version: 6.13.0 resolution: "@ledgerhq/logs@npm:6.13.0" @@ -3375,13 +3455,21 @@ __metadata: languageName: node linkType: hard -"@ledgerhq/types-live@npm:^6.80.0, @ledgerhq/types-live@npm:^6.89.0": - version: 6.89.0 - resolution: "@ledgerhq/types-live@npm:6.89.0" +"@ledgerhq/logs@npm:^6.14.0": + version: 6.14.0 + resolution: "@ledgerhq/logs@npm:6.14.0" + checksum: 10/b30b6aeef9df181efe99476a84f95b89ada695c038fbc3f915410f0bae244a342df642f003582c71222b4388cdc58d281e88ae2532ba85911f580d0cbfa4bd3a + languageName: node + linkType: hard + +"@ledgerhq/types-live@npm:^6.90.0, @ledgerhq/types-live@npm:^6.97.0": + version: 6.97.0 + resolution: "@ledgerhq/types-live@npm:6.97.0" dependencies: + "@ledgerhq/client-ids": "npm:0.5.1" bignumber.js: "npm:^9.1.2" - rxjs: "npm:^7.8.1" - checksum: 10/b53458db34c4179ee8de592d854d2df1530078cdfba097f93931aeb4ae3f4424a7ef69239cff855e6f45e6f13a5f585e894f2744812143ef4da558396cdb8655 + rxjs: "npm:7.8.2" + checksum: 10/4fe682b96e91ac89fd21e45d815844d7f28c50500ebda6514cfbdef4248f152f76945f4c2070365712cab54025f00244895c7043d18a4debd28253f3d0db1f11 languageName: node linkType: hard @@ -3561,6 +3649,29 @@ __metadata: languageName: node linkType: hard +"@mysten/sui@npm:^1.0.5, @mysten/sui@npm:^1.3.0, @mysten/sui@npm:^1.45.2": + version: 1.45.2 + resolution: "@mysten/sui@npm:1.45.2" + dependencies: + "@graphql-typed-document-node/core": "npm:^3.2.0" + "@mysten/bcs": "npm:1.9.2" + "@mysten/utils": "npm:0.2.0" + "@noble/curves": "npm:=1.9.4" + "@noble/hashes": "npm:^1.8.0" + "@protobuf-ts/grpcweb-transport": "npm:^2.11.1" + "@protobuf-ts/runtime": "npm:^2.11.1" + "@protobuf-ts/runtime-rpc": "npm:^2.11.1" + "@scure/base": "npm:^1.2.6" + "@scure/bip32": "npm:^1.7.0" + "@scure/bip39": "npm:^1.6.0" + gql.tada: "npm:^1.8.13" + graphql: "npm:^16.11.0" + poseidon-lite: "npm:0.2.1" + valibot: "npm:^1.2.0" + checksum: 10/92d3e89251e2c8baeff9ce745e7e24ab588a1cc6aac7c9c7b7046e51880d2bbb6e9a3326ef48d212429bd14342eca48216097d2e89e5c64111c6d16b10d0acbe + languageName: node + linkType: hard + "@mysten/utils@npm:0.2.0": version: 0.2.0 resolution: "@mysten/utils@npm:0.2.0" @@ -3581,6 +3692,232 @@ __metadata: languageName: node linkType: hard +"@near-js/accounts@npm:1.0.4": + version: 1.0.4 + resolution: "@near-js/accounts@npm:1.0.4" + dependencies: + "@near-js/crypto": "npm:1.2.1" + "@near-js/providers": "npm:0.1.1" + "@near-js/signers": "npm:0.1.1" + "@near-js/transactions": "npm:1.1.2" + "@near-js/types": "npm:0.0.4" + "@near-js/utils": "npm:0.1.0" + ajv: "npm:8.11.2" + ajv-formats: "npm:2.1.1" + bn.js: "npm:5.2.1" + borsh: "npm:1.0.0" + depd: "npm:2.0.0" + lru_map: "npm:0.4.1" + near-abi: "npm:0.1.1" + checksum: 10/6f97fb832db5d3389c593902912d717afd35743464731791e8d6128189a09240fdbe1ccc82612fdf506c3f09173df0bea10a1e0e0b1019fa32ad9c1083f7a059 + languageName: node + linkType: hard + +"@near-js/crypto@npm:1.2.1": + version: 1.2.1 + resolution: "@near-js/crypto@npm:1.2.1" + dependencies: + "@near-js/types": "npm:0.0.4" + "@near-js/utils": "npm:0.1.0" + "@noble/curves": "npm:1.2.0" + bn.js: "npm:5.2.1" + borsh: "npm:1.0.0" + randombytes: "npm:2.1.0" + checksum: 10/f5787eab26687183949da4d4a241b268b5a61f3e9ff06575f8a1f27a1b4c583aeef3e35c1dcef03eed13c68c44eb66ab7cad3d662c8c057a0dd2ef3dda50dad7 + languageName: node + linkType: hard + +"@near-js/crypto@npm:2.5.1, @near-js/crypto@npm:^2.5.1": + version: 2.5.1 + resolution: "@near-js/crypto@npm:2.5.1" + dependencies: + "@near-js/types": "npm:2.5.1" + "@near-js/utils": "npm:2.5.1" + "@noble/curves": "npm:1.8.1" + "@noble/hashes": "npm:^1.7.1" + borsh: "npm:1.0.0" + secp256k1: "npm:5.0.1" + peerDependencies: + "@near-js/types": ^2.0.1 + "@near-js/utils": ^2.0.1 + checksum: 10/bc4a07d4e30a9067dbb1b84b32f19f065cf2c77c5e48a04ffbb7a1497877fb945e7af78bbc6a865ebfc411a79e0832507307f9927d8e22711a20c17a0e3f6675 + languageName: node + linkType: hard + +"@near-js/keystores-browser@npm:0.0.9": + version: 0.0.9 + resolution: "@near-js/keystores-browser@npm:0.0.9" + dependencies: + "@near-js/crypto": "npm:1.2.1" + "@near-js/keystores": "npm:0.0.9" + checksum: 10/ba9a1b023b14f8b16282c0255b8d2dbe008ebabffab497d4d0f2c6cc4fbff981c48a8b34b8e405ff193a3fe1c329b459c1452b2355ebe052e40691fcfa76d62b + languageName: node + linkType: hard + +"@near-js/keystores-node@npm:0.0.9": + version: 0.0.9 + resolution: "@near-js/keystores-node@npm:0.0.9" + dependencies: + "@near-js/crypto": "npm:1.2.1" + "@near-js/keystores": "npm:0.0.9" + checksum: 10/03e8b1e011f9ee92c773d52827e03db005634edb3fa3bbe5b19b6bae9b86ea685e9897ca068eb7d7e046a869441512ff71c0e991e06f24a3aaeaa9d0cd73b565 + languageName: node + linkType: hard + +"@near-js/keystores@npm:0.0.9": + version: 0.0.9 + resolution: "@near-js/keystores@npm:0.0.9" + dependencies: + "@near-js/crypto": "npm:1.2.1" + "@near-js/types": "npm:0.0.4" + checksum: 10/da903c01d302d7c0a4c9c8ec359b62611f586a4d63ff2eeb8d1f385c37745482cb7529f5f8dda9e1c9f97314f304a058295cc3db9baa18fb727bf135f15c5250 + languageName: node + linkType: hard + +"@near-js/providers@npm:0.1.1": + version: 0.1.1 + resolution: "@near-js/providers@npm:0.1.1" + dependencies: + "@near-js/transactions": "npm:1.1.2" + "@near-js/types": "npm:0.0.4" + "@near-js/utils": "npm:0.1.0" + bn.js: "npm:5.2.1" + borsh: "npm:1.0.0" + http-errors: "npm:1.7.2" + node-fetch: "npm:2.6.7" + dependenciesMeta: + node-fetch: + optional: true + checksum: 10/906285d0655705bb14e9b8a030aea4340ab900fd1bf20ea0c99f704dd572602b1362984a562a066a318a3caa5cb1dadcc0f1ea67f4728cc06d881d53fa31d1ac + languageName: node + linkType: hard + +"@near-js/providers@npm:^2.5.1": + version: 2.5.1 + resolution: "@near-js/providers@npm:2.5.1" + dependencies: + "@near-js/crypto": "npm:2.5.1" + "@near-js/transactions": "npm:2.5.1" + "@near-js/types": "npm:2.5.1" + "@near-js/utils": "npm:2.5.1" + borsh: "npm:1.0.0" + exponential-backoff: "npm:^3.1.2" + node-fetch: "npm:2.6.7" + peerDependencies: + "@near-js/crypto": ^2.0.1 + "@near-js/transactions": ^2.0.1 + "@near-js/types": ^2.0.1 + "@near-js/utils": ^2.0.1 + dependenciesMeta: + node-fetch: + optional: true + checksum: 10/b00cfa1ac612dbd7c9ee50bdc016eda2ee88e665735906f236ccbb32d5f91b07c027cc238c53aeab1c691d5c8c38f62f0a06a1e96f9d1ed1d11cd58a627f3e5c + languageName: node + linkType: hard + +"@near-js/signers@npm:0.1.1": + version: 0.1.1 + resolution: "@near-js/signers@npm:0.1.1" + dependencies: + "@near-js/crypto": "npm:1.2.1" + "@near-js/keystores": "npm:0.0.9" + "@noble/hashes": "npm:1.3.3" + checksum: 10/cf0d401363d195ee54c52f8329c527b86ddd26a7c2ee0047046fed5584514f51ec66a0a36031384c50707e0cf9113ad4d4dba7d137e847ca6041ab5d8a7cb0ba + languageName: node + linkType: hard + +"@near-js/transactions@npm:1.1.2": + version: 1.1.2 + resolution: "@near-js/transactions@npm:1.1.2" + dependencies: + "@near-js/crypto": "npm:1.2.1" + "@near-js/signers": "npm:0.1.1" + "@near-js/types": "npm:0.0.4" + "@near-js/utils": "npm:0.1.0" + "@noble/hashes": "npm:1.3.3" + bn.js: "npm:5.2.1" + borsh: "npm:1.0.0" + checksum: 10/3b31115fc56971b46337d60a198702cc557ee1c540ccdf16ce1e46b90033bccf2dadedb8d6a4cd2d425ceafd5e5804802a6854c287d7828f142456bfa429b1ca + languageName: node + linkType: hard + +"@near-js/transactions@npm:2.5.1, @near-js/transactions@npm:^2.5.1": + version: 2.5.1 + resolution: "@near-js/transactions@npm:2.5.1" + dependencies: + "@near-js/crypto": "npm:2.5.1" + "@near-js/types": "npm:2.5.1" + "@near-js/utils": "npm:2.5.1" + "@noble/hashes": "npm:1.7.1" + borsh: "npm:1.0.0" + peerDependencies: + "@near-js/crypto": ^2.0.1 + "@near-js/types": ^2.0.1 + "@near-js/utils": ^2.0.1 + checksum: 10/b68d869819681003cd9db2d076972fefa1b57c1303e0075c5ec1082acc44fb98f42fcd6f56cb52450e3c8119da835a07ce5eeb17a796af856b4d2a746e8bef37 + languageName: node + linkType: hard + +"@near-js/types@npm:0.0.4": + version: 0.0.4 + resolution: "@near-js/types@npm:0.0.4" + dependencies: + bn.js: "npm:5.2.1" + checksum: 10/0adcb5ba847824afc78f4a6c61a8085e19660023b4b950b56bd186ff33c84f1f6c0ed36a082e6aa60f8fd0dc23e2f858105b7932127ec3c65f1c2c28dc0c980f + languageName: node + linkType: hard + +"@near-js/types@npm:2.5.1": + version: 2.5.1 + resolution: "@near-js/types@npm:2.5.1" + checksum: 10/66d1d3fcca049ea7a84a8707b8fc0132ff48715d099d0ba61fc044ec9502de8526a6a7f037748619bb362e051fe5c70846e4ae7c633fa11078fdd0457da67e3c + languageName: node + linkType: hard + +"@near-js/utils@npm:0.1.0": + version: 0.1.0 + resolution: "@near-js/utils@npm:0.1.0" + dependencies: + "@near-js/types": "npm:0.0.4" + bn.js: "npm:5.2.1" + bs58: "npm:4.0.0" + depd: "npm:2.0.0" + mustache: "npm:4.0.0" + checksum: 10/219605253aa74f95cf72270ab268768797459f6951928488cad9aeedf297496125e7eba31659b673a034161ceaf6ff9ed5e7550a0b6692004c7e453873acf695 + languageName: node + linkType: hard + +"@near-js/utils@npm:2.5.1, @near-js/utils@npm:^2.5.1": + version: 2.5.1 + resolution: "@near-js/utils@npm:2.5.1" + dependencies: + "@near-js/types": "npm:2.5.1" + "@scure/base": "npm:^1.2.4" + depd: "npm:2.0.0" + mustache: "npm:4.0.0" + peerDependencies: + "@near-js/types": ^2.0.1 + checksum: 10/e4566e20c575cd00ab15a53f7e2f106b4d92ac497a07afe35d4057147e3a84f8596a914db55acc2b552ebb52537e35d1769828b8d9e57b085ccc4c3d0d0fea12 + languageName: node + linkType: hard + +"@near-js/wallet-account@npm:1.1.1": + version: 1.1.1 + resolution: "@near-js/wallet-account@npm:1.1.1" + dependencies: + "@near-js/accounts": "npm:1.0.4" + "@near-js/crypto": "npm:1.2.1" + "@near-js/keystores": "npm:0.0.9" + "@near-js/signers": "npm:0.1.1" + "@near-js/transactions": "npm:1.1.2" + "@near-js/types": "npm:0.0.4" + "@near-js/utils": "npm:0.1.0" + bn.js: "npm:5.2.1" + borsh: "npm:1.0.0" + checksum: 10/1a50e7c5898880403f0f5f7eaf660e9b69080084c5b918453bbac11f52f683238e359b55f8f01a288da7fb9b8da9e45f38dba162abd09c9b9738dca12c2aca3b + languageName: node + linkType: hard + "@nestjs/axios@npm:^4.0.1": version: 4.0.1 resolution: "@nestjs/axios@npm:4.0.1" @@ -3815,6 +4152,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:1.8.1": + version: 1.8.1 + resolution: "@noble/curves@npm:1.8.1" + dependencies: + "@noble/hashes": "npm:1.7.1" + checksum: 10/e861db372cc0734b02a4c61c0f5a6688d4a7555edca3d8a9e7c846c9aa103ca52d3c3818e8bc333a1a95b5be7f370ff344668d5d759471b11c2d14c7f24b3984 + languageName: node + linkType: hard + "@noble/curves@npm:1.9.1": version: 1.9.1 resolution: "@noble/curves@npm:1.9.1" @@ -3824,7 +4170,7 @@ __metadata: languageName: node linkType: hard -"@noble/curves@npm:1.9.7, @noble/curves@npm:^1.4.2, @noble/curves@npm:^1.7.0, @noble/curves@npm:~1.9.0": +"@noble/curves@npm:1.9.7, @noble/curves@npm:^1.4.2, @noble/curves@npm:^1.6.0, @noble/curves@npm:^1.7.0, @noble/curves@npm:~1.9.0": version: 1.9.7 resolution: "@noble/curves@npm:1.9.7" dependencies: @@ -3842,6 +4188,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:~1.7.0": + version: 1.7.0 + resolution: "@noble/curves@npm:1.7.0" + dependencies: + "@noble/hashes": "npm:1.6.0" + checksum: 10/2a11ef4895907d0b241bd3b72f9e6ebe56f0e705949bfd5efe003f25233549f620d287550df2d24ad56a1f953b82ec5f7cf4bd7cb78b1b2e76eb6dd516d44cf8 + languageName: node + linkType: hard + "@noble/hashes@npm:1.3.2": version: 1.3.2 resolution: "@noble/hashes@npm:1.3.2" @@ -3849,6 +4204,13 @@ __metadata: languageName: node linkType: hard +"@noble/hashes@npm:1.3.3": + version: 1.3.3 + resolution: "@noble/hashes@npm:1.3.3" + checksum: 10/1025ddde4d24630e95c0818e63d2d54ee131b980fe113312d17ed7468bc18f54486ac86c907685759f8a7e13c2f9b9e83ec7b67d1cc20836f36b5e4a65bb102d + languageName: node + linkType: hard + "@noble/hashes@npm:1.4.0, @noble/hashes@npm:~1.4.0": version: 1.4.0 resolution: "@noble/hashes@npm:1.4.0" @@ -3856,13 +4218,34 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:1.8.0, @noble/hashes@npm:^1, @noble/hashes@npm:^1.0.0, @noble/hashes@npm:^1.2.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.4.0, @noble/hashes@npm:^1.8.0, @noble/hashes@npm:~1.8.0": +"@noble/hashes@npm:1.6.0": + version: 1.6.0 + resolution: "@noble/hashes@npm:1.6.0" + checksum: 10/b44b043b02adbecd33596adeed97d9f9864c24a2410f7ac3b847986c2ecf1f6f0df76024b3f1b14d6ea954932960d88898fe551fb9d39844a8b870e9f9044ea1 + languageName: node + linkType: hard + +"@noble/hashes@npm:1.7.1": + version: 1.7.1 + resolution: "@noble/hashes@npm:1.7.1" + checksum: 10/ca3120da0c3e7881d6a481e9667465cc9ebbee1329124fb0de442e56d63fef9870f8cc96f264ebdb18096e0e36cebc0e6e979a872d545deb0a6fed9353f17e05 + languageName: node + linkType: hard + +"@noble/hashes@npm:1.8.0, @noble/hashes@npm:^1, @noble/hashes@npm:^1.0.0, @noble/hashes@npm:^1.2.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.4.0, @noble/hashes@npm:^1.5.0, @noble/hashes@npm:^1.7.1, @noble/hashes@npm:^1.8.0, @noble/hashes@npm:~1.8.0": version: 1.8.0 resolution: "@noble/hashes@npm:1.8.0" checksum: 10/474b7f56bc6fb2d5b3a42132561e221b0ea4f91e590f4655312ca13667840896b34195e2b53b7f097ec080a1fdd3b58d902c2a8d0fbdf51d2e238b53808a177e languageName: node linkType: hard +"@noble/hashes@npm:~1.6.0": + version: 1.6.1 + resolution: "@noble/hashes@npm:1.6.1" + checksum: 10/74d9ad7b1437a22ba3b877584add3367587fbf818113152f293025d20d425aa74c191d18d434797312f2270458bc9ab3241c34d14ec6115fb16438b3248f631f + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -4710,7 +5093,51 @@ __metadata: languageName: node linkType: hard -"@scure/base@npm:^1.1.3, @scure/base@npm:^1.2.6, @scure/base@npm:~1.2.5": +"@pythnetwork/hermes-client@npm:2.1.0": + version: 2.1.0 + resolution: "@pythnetwork/hermes-client@npm:2.1.0" + dependencies: + "@zodios/core": "npm:^10.9.6" + eventsource: "npm:^3.0.5" + zod: "npm:^3.23.8" + checksum: 10/77ce8b83b9350b14d181d52dc743839d79599976ccd737f40be6cc79a03b1c8b5e7fbd4a0f874ac63bcfdf93439d8bea00cf07556753085607355eafb0df155d + languageName: node + linkType: hard + +"@pythnetwork/pyth-sui-js@npm:^2.1.0": + version: 2.4.0 + resolution: "@pythnetwork/pyth-sui-js@npm:2.4.0" + dependencies: + "@mysten/sui": "npm:^1.3.0" + "@pythnetwork/hermes-client": "npm:2.1.0" + buffer: "npm:^6.0.3" + checksum: 10/d202270aee2fa2575305cf2ad512c11cedbfa6e40dd080f91400a509ecb2c5fe0c38234773d87128b2f6b5e8d69d240774cb6a46f4cd0c9109f390fd50e38bc3 + languageName: node + linkType: hard + +"@reduxjs/toolkit@npm:2.11.2": + version: 2.11.2 + resolution: "@reduxjs/toolkit@npm:2.11.2" + dependencies: + "@standard-schema/spec": "npm:^1.0.0" + "@standard-schema/utils": "npm:^0.3.0" + immer: "npm:^11.0.0" + redux: "npm:^5.0.1" + redux-thunk: "npm:^3.1.0" + reselect: "npm:^5.1.0" + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + checksum: 10/2e1098c7fcd10b770344d0b39c58a3b9cb3aec7da0979c02f1ac1ef8f56619ac787c623e480bef0cf6f2c05f1c13fd4d44045c794e2c2d3460f9bf0d2875995c + languageName: node + linkType: hard + +"@scure/base@npm:^1.1.3, @scure/base@npm:^1.2.4, @scure/base@npm:^1.2.6, @scure/base@npm:~1.2.1, @scure/base@npm:~1.2.5": version: 1.2.6 resolution: "@scure/base@npm:1.2.6" checksum: 10/c1a7bd5e0b0c8f94c36fbc220f4a67cc832b00e2d2065c7d8a404ed81ab1c94c5443def6d361a70fc382db3496e9487fb9941728f0584782b274c18a4bed4187 @@ -4735,7 +5162,7 @@ __metadata: languageName: node linkType: hard -"@scure/bip32@npm:1.7.0, @scure/bip32@npm:^1.7.0": +"@scure/bip32@npm:1.7.0, @scure/bip32@npm:^1.5.0, @scure/bip32@npm:^1.7.0": version: 1.7.0 resolution: "@scure/bip32@npm:1.7.0" dependencies: @@ -4756,7 +5183,7 @@ __metadata: languageName: node linkType: hard -"@scure/bip39@npm:1.6.0, @scure/bip39@npm:^1.6.0": +"@scure/bip39@npm:1.6.0, @scure/bip39@npm:^1.4.0, @scure/bip39@npm:^1.6.0": version: 1.6.0 resolution: "@scure/bip39@npm:1.6.0" dependencies: @@ -4766,6 +5193,16 @@ __metadata: languageName: node linkType: hard +"@scure/starknet@npm:1.1.0": + version: 1.1.0 + resolution: "@scure/starknet@npm:1.1.0" + dependencies: + "@noble/curves": "npm:~1.7.0" + "@noble/hashes": "npm:~1.6.0" + checksum: 10/88d846584ccf7ab213472382461d8db5a5b50e1b638205122935e3aff214cbba0703f75ed4d63ed4b1f820fe3c5b08b86013c9c0cc855fd197e36bda4d86f274 + languageName: node + linkType: hard + "@shapeshift/notifications-service@workspace:apps/notifications-service": version: 0.0.0-use.local resolution: "@shapeshift/notifications-service@workspace:apps/notifications-service" @@ -4830,6 +5267,21 @@ __metadata: languageName: node linkType: hard +"@shapeshiftoss/bitcoinjs-lib@npm:7.0.0-shapeshift.2": + version: 7.0.0-shapeshift.2 + resolution: "@shapeshiftoss/bitcoinjs-lib@npm:7.0.0-shapeshift.2" + dependencies: + "@noble/hashes": "npm:^1.2.0" + bech32: "npm:^2.0.0" + bip174: "npm:^3.0.0" + bs58check: "npm:^4.0.0" + uint8array-tools: "npm:^0.0.9" + valibot: "npm:^0.38.0" + varuint-bitcoin: "npm:^2.0.0" + checksum: 10/50fd1eceb8b257bea8dbce6fb4df9339dd4cfc2ac8df79430328d84030b617a836bdc77af3c911ace7d76fe9d4c2732043f2d696ac49115eb9be080f343e93e3 + languageName: node + linkType: hard + "@shapeshiftoss/blockbook@npm:^9.3.0": version: 9.3.0 resolution: "@shapeshiftoss/blockbook@npm:9.3.0" @@ -4848,39 +5300,46 @@ __metadata: languageName: node linkType: hard -"@shapeshiftoss/caip@npm:^8.16.5": - version: 8.16.5 - resolution: "@shapeshiftoss/caip@npm:8.16.5" +"@shapeshiftoss/caip@npm:^8.16.7, @shapeshiftoss/caip@npm:^8.16.8": + version: 8.16.8 + resolution: "@shapeshiftoss/caip@npm:8.16.8" dependencies: - axios: "npm:^1.13.0" - checksum: 10/dfca57633622596dea44a8d7efdebc32623df1f93b142d252b538a9165b232716c058a41806c9b001f06dd1c3be8b97550b04ec4d3f50d37c8a5a0486aac9cd6 + axios: "npm:^1.13.5" + checksum: 10/d662758523eeb7a1f5ab940119cc29703295d4dd3dde2752553b2ade0d61e6d3b8339fdefef418ffddf245d5f7ea541ea47334b71cdae63f90dcb9d1d56f3a5e languageName: node linkType: hard -"@shapeshiftoss/chain-adapters@npm:11.3.6, @shapeshiftoss/chain-adapters@npm:^11.3.6": - version: 11.3.6 - resolution: "@shapeshiftoss/chain-adapters@npm:11.3.6" +"@shapeshiftoss/chain-adapters@npm:11.3.9, @shapeshiftoss/chain-adapters@npm:^11.3.9": + version: 11.3.9 + resolution: "@shapeshiftoss/chain-adapters@npm:11.3.9" dependencies: "@mysten/sui": "npm:1.45.0" - "@shapeshiftoss/caip": "npm:^8.16.5" - "@shapeshiftoss/hdwallet-core": "npm:1.62.21" - "@shapeshiftoss/hdwallet-ledger": "npm:1.62.21" - "@shapeshiftoss/types": "npm:^8.6.5" - "@shapeshiftoss/unchained-client": "npm:^10.14.8" - "@shapeshiftoss/utils": "npm:^1.0.4" + "@near-js/crypto": "npm:^2.5.1" + "@near-js/providers": "npm:^2.5.1" + "@near-js/transactions": "npm:^2.5.1" + "@near-js/utils": "npm:^2.5.1" + "@shapeshiftoss/caip": "npm:^8.16.7" + "@shapeshiftoss/hdwallet-core": "npm:^1.62.41" + "@shapeshiftoss/hdwallet-ledger": "npm:^1.62.41" + "@shapeshiftoss/types": "npm:^8.6.7" + "@shapeshiftoss/unchained-client": "npm:^10.14.10" + "@shapeshiftoss/utils": "npm:^1.0.6" "@solana/spl-token": "npm:^0.4.9" - "@solana/web3.js": "npm:^1.98.0" - axios: "npm:^1.13.0" + "@solana/web3.js": "npm:1.98.0" + "@ton/core": "npm:^0.62.1" + "@ton/crypto": "npm:^3.3.0" + axios: "npm:^1.13.5" bech32: "npm:^2.0.0" bignumber.js: "npm:^9.3.1" bs58check: "npm:^2.1.2" coinselect: "npm:^3.1.13" - lodash: "npm:^4.17.21" + ethers: "npm:6.11.1" + lodash: "npm:^4.17.23" multicoin-address-validator: "npm:^0.5.12" node-polyglot: "npm:^2.4.0" p-queue: "npm:^8.0.1" - viem: "npm:^2.40.3" - checksum: 10/039e5894d4ddc91dc0ed652b9e2339538da5d85995c3881c61314e317b621ce7b6d98ae88927c5e3e236d72fec2ee00c264a086e5730a214e4665db51984de07 + viem: "npm:2.43.5" + checksum: 10/90eb0699245d0382a3c769dd9fad1102192bcb08e0cf932da9657ddd0d9ba1f66ecc9f6b41f466e7f966125fbd87a8fee61a3c25f606a88931dcb988dbecade9 languageName: node linkType: hard @@ -4910,66 +5369,68 @@ __metadata: languageName: node linkType: hard -"@shapeshiftoss/contracts@npm:^1.0.4": - version: 1.0.4 - resolution: "@shapeshiftoss/contracts@npm:1.0.4" +"@shapeshiftoss/contracts@npm:^1.0.6": + version: 1.0.6 + resolution: "@shapeshiftoss/contracts@npm:1.0.6" dependencies: - "@shapeshiftoss/caip": "npm:^8.16.5" - "@shapeshiftoss/types": "npm:^8.6.5" - "@shapeshiftoss/utils": "npm:^1.0.4" + "@shapeshiftoss/caip": "npm:^8.16.8" + "@shapeshiftoss/types": "npm:^8.6.7" + "@shapeshiftoss/utils": "npm:^1.0.6" "@uniswap/sdk": "npm:^3.0.3" ethers: "npm:6.11.1" ethers5: "npm:ethers@5.7.2" - lodash: "npm:^4.17.21" - viem: "npm:^2.40.3" - checksum: 10/74ab053b34f84cc8ead1aff207243fa059b6c35acebbdb9994d438254ebb750ffc44421f73b59852577a8af38714c8051603a81c81bba90a088b131fd9c76bd7 + lodash: "npm:^4.17.23" + viem: "npm:2.43.5" + checksum: 10/c3b4876f98dc746caf22baa3e83c4faeb5a79b5db9c10366eb86735976a053a695aeecad6296d9c8f1e2a7ea7c7cb014ba304b9b3999626657bbe51d662e7c78 languageName: node linkType: hard -"@shapeshiftoss/hdwallet-core@npm:1.62.21": - version: 1.62.21 - resolution: "@shapeshiftoss/hdwallet-core@npm:1.62.21" +"@shapeshiftoss/hdwallet-core@npm:^1.62.41": + version: 1.62.41 + resolution: "@shapeshiftoss/hdwallet-core@npm:1.62.41" dependencies: - "@shapeshiftoss/bitcoinjs-lib": "npm:7.0.0-shapeshift.0" + "@shapeshiftoss/bitcoinjs-lib": "npm:7.0.0-shapeshift.2" "@shapeshiftoss/proto-tx-builder": "npm:0.10.0" "@solana/web3.js": "npm:1.95.8" + bs58check: "npm:^4.0.0" eip-712: "npm:^1.0.0" ethers: "npm:5.7.2" eventemitter2: "npm:^5.0.1" lodash: "npm:^4.17.21" rxjs: "npm:^6.4.0" type-assertions: "npm:^1.1.0" - checksum: 10/ca1a64aeaafdd7d927c792daf2157d7b0695d097e666d3eafc167f5d9744161daf0fe7067384325123e5b4a1997fa5834ac2c1276f896af38cfd7b46b55dfbaa + checksum: 10/5428159ad5834b6efc042da8484fe5db75fb7daba161f764deb1c5bbd76e4ba7426427cd66d8a93501db1f42ced79b9cdafce5ef4b75b69120b0ae20baf53e69 languageName: node linkType: hard -"@shapeshiftoss/hdwallet-ledger@npm:1.62.21": - version: 1.62.21 - resolution: "@shapeshiftoss/hdwallet-ledger@npm:1.62.21" +"@shapeshiftoss/hdwallet-ledger@npm:^1.62.41": + version: 1.62.41 + resolution: "@shapeshiftoss/hdwallet-ledger@npm:1.62.41" dependencies: "@ethereumjs/common": "npm:3.2.0" "@ethereumjs/tx": "npm:4.2.0" - "@ledgerhq/device-core": "npm:^0.5.4" - "@ledgerhq/hw-app-btc": "npm:^10.10.0" - "@ledgerhq/hw-app-cosmos": "npm:^6.32.4" - "@ledgerhq/hw-app-eth": "npm:^6.45.13" - "@ledgerhq/hw-app-solana": "npm:^7.5.1" - "@ledgerhq/hw-app-trx": "npm:^6.31.9" - "@ledgerhq/hw-transport": "npm:6.31.8" + "@ledgerhq/device-core": "npm:0.6.9" + "@ledgerhq/hw-app-btc": "npm:10.13.0" + "@ledgerhq/hw-app-cosmos": "npm:6.32.9" + "@ledgerhq/hw-app-eth": "npm:7.0.0" + "@ledgerhq/hw-app-near": "npm:6.31.10" + "@ledgerhq/hw-app-solana": "npm:7.6.0" + "@ledgerhq/hw-app-trx": "npm:6.31.9" + "@ledgerhq/hw-transport": "npm:6.31.13" "@ledgerhq/logs": "npm:6.13.0" "@mysten/ledgerjs-hw-app-sui": "npm:^0.7.0" - "@shapeshiftoss/bitcoinjs-lib": "npm:7.0.0-shapeshift.0" - "@shapeshiftoss/hdwallet-core": "npm:1.62.21" + "@shapeshiftoss/bitcoinjs-lib": "npm:7.0.0-shapeshift.2" + "@shapeshiftoss/hdwallet-core": "npm:^1.62.41" "@solana/web3.js": "npm:1.95.8" base64-js: "npm:^1.5.1" bchaddrjs: "npm:^0.4.4" bitcoinjs-message: "npm:^2.0.0" - bs58check: "npm:2.1.2" + bs58check: "npm:^4.0.0" ethereumjs-tx: "npm:1.3.7" ethereumjs-util: "npm:^6.1.0" ethers: "npm:5.7.2" lodash: "npm:^4.17.21" - checksum: 10/b9d8d5f1225f59f114da27a6558f3d6c12e723ed6a2f2da0ea3143eae8555ab257b99f5c8e95bd3c43d0508d0089630382b3a331becd7c49d8cbde5a6fcfa816 + checksum: 10/ea629f2b95c7f472262b271ff56aed2d0db0ddf317da75b812d1b4734a78e83e8eba04a12f089bb814921014f8ff378522e94b64291c2b437e7195b9f4181dad languageName: node linkType: hard @@ -4990,72 +5451,75 @@ __metadata: languageName: node linkType: hard -"@shapeshiftoss/swapper@npm:17.6.7": - version: 17.6.7 - resolution: "@shapeshiftoss/swapper@npm:17.6.7" +"@shapeshiftoss/swapper@npm:17.6.10": + version: 17.6.10 + resolution: "@shapeshiftoss/swapper@npm:17.6.10" dependencies: "@arbitrum/sdk": "npm:^4.0.1" + "@avnu/avnu-sdk": "npm:^4.0.1" "@coral-xyz/anchor": "npm:0.29.0" "@cowprotocol/app-data": "npm:^2.3.0" "@defuse-protocol/one-click-sdk-typescript": "npm:^0.1.1-0.2" "@jup-ag/api": "npm:^6.0.30" + "@mysten/sui": "npm:^1.45.2" "@shapeshiftoss/bitcoinjs-lib": "npm:7.0.0-shapeshift.0" - "@shapeshiftoss/caip": "npm:^8.16.5" - "@shapeshiftoss/chain-adapters": "npm:^11.3.6" - "@shapeshiftoss/contracts": "npm:^1.0.4" - "@shapeshiftoss/hdwallet-core": "npm:1.62.21" - "@shapeshiftoss/types": "npm:^8.6.5" - "@shapeshiftoss/unchained-client": "npm:^10.14.8" - "@shapeshiftoss/utils": "npm:^1.0.4" + "@shapeshiftoss/caip": "npm:^8.16.7" + "@shapeshiftoss/chain-adapters": "npm:^11.3.9" + "@shapeshiftoss/contracts": "npm:^1.0.6" + "@shapeshiftoss/hdwallet-core": "npm:^1.62.41" + "@shapeshiftoss/types": "npm:^8.6.7" + "@shapeshiftoss/unchained-client": "npm:^10.14.10" + "@shapeshiftoss/utils": "npm:^1.0.6" "@sniptt/monads": "npm:^0.5.10" - "@solana/web3.js": "npm:^1.98.0" + "@solana/web3.js": "npm:1.98.0" + "@ston-fi/omniston-sdk": "npm:^0.7.8" "@uniswap/sdk-core": "npm:^5.3.1" "@uniswap/v3-sdk": "npm:^3.13.1" - axios: "npm:^1.13.0" - axios-cache-interceptor: "npm:^1.5.3" + axios: "npm:^1.13.5" + axios-cache-interceptor: "npm:^1.11.1" bignumber.js: "npm:^9.3.1" eip-712: "npm:^1.0.0" ethers: "npm:6.11.1" ethers5: "npm:ethers@5.7.2" - lodash: "npm:^4.17.21" + lodash: "npm:^4.17.23" mixpanel-browser: "npm:^2.67.0" node-polyglot: "npm:^2.4.0" pretty-ms: "npm:7.0.1" - qs: "npm:^6.10.5" + qs: "npm:^6.14.2" retry-axios: "npm:^2.6.0" uuid: "npm:^9.0.0" - viem: "npm:^2.40.3" - checksum: 10/a6c9d59ca290b13c212296f8cebba9a52770c4beb1c43023a128c5f6fbe82216dd7815125818e5a22d77f9e210dd7eab371ee07e983fd2e7d571e6cc62505ab0 + viem: "npm:2.43.5" + checksum: 10/124949f2fa1fc8fe0b405da561ac7c95adb0ff2de7f8c6764247645fe9dd95a37027247d79ab78ffc18addbe13affadd02cdf55c38a1eae5965b31dd98bc3554 languageName: node linkType: hard -"@shapeshiftoss/types@npm:8.6.5, @shapeshiftoss/types@npm:^8.6.5": - version: 8.6.5 - resolution: "@shapeshiftoss/types@npm:8.6.5" +"@shapeshiftoss/types@npm:8.6.7, @shapeshiftoss/types@npm:^8.6.7": + version: 8.6.7 + resolution: "@shapeshiftoss/types@npm:8.6.7" dependencies: "@cowprotocol/app-data": "npm:^2.3.0" - "@shapeshiftoss/caip": "npm:^8.16.5" + "@shapeshiftoss/caip": "npm:^8.16.8" ethers5: "npm:ethers@5.7.2" - viem: "npm:^2.40.3" - checksum: 10/2e69f7c7eaa7145db9713cc17c4768b87082f4c48c4604bf4c64b4eb8ef9e595e21175dcdb75314e58d01cce37e081a026359c6e30a7a0b84e5aa7711ed738b5 + viem: "npm:2.43.5" + checksum: 10/3682c379153f223d2ef912da374688847e6250d69083d788371fb5a11e34cc495b24e333a7634e080083d19d2d58b9576749d72a2d2166f57e89b5a6465e6ba4 languageName: node linkType: hard -"@shapeshiftoss/unchained-client@npm:10.14.8, @shapeshiftoss/unchained-client@npm:^10.14.8": - version: 10.14.8 - resolution: "@shapeshiftoss/unchained-client@npm:10.14.8" +"@shapeshiftoss/unchained-client@npm:10.14.10, @shapeshiftoss/unchained-client@npm:^10.14.10": + version: 10.14.10 + resolution: "@shapeshiftoss/unchained-client@npm:10.14.10" dependencies: - "@shapeshiftoss/caip": "npm:^8.16.5" + "@shapeshiftoss/caip": "npm:^8.16.8" "@shapeshiftoss/common-api": "npm:^9.3.0" - "@shapeshiftoss/contracts": "npm:^1.0.4" - "@shapeshiftoss/utils": "npm:^1.0.4" - axios: "npm:^1.13.0" + "@shapeshiftoss/contracts": "npm:^1.0.6" + "@shapeshiftoss/utils": "npm:^1.0.6" + axios: "npm:^1.13.5" bignumber.js: "npm:^9.3.1" ethers: "npm:6.11.1" isomorphic-ws: "npm:^4.0.1" - viem: "npm:^2.40.3" + viem: "npm:2.43.5" ws: "npm:^8.17.1" - checksum: 10/445f4c888e720a923c12f2602dcde903ffeb03467bee4c7c2f50916aee4830a997f1a8113995aaefe29226a06d68a6e999d54ab4e527ca93cd94777976e27dad + checksum: 10/08059a626b4b0275000ed0c645413ab735f2fbb1514ba1b235e2957f18b3acc247c925fee4320314ee9d265fba6c79ff8e77aae8636901af3f00ce773054a90e languageName: node linkType: hard @@ -5069,17 +5533,17 @@ __metadata: languageName: node linkType: hard -"@shapeshiftoss/utils@npm:^1.0.4": - version: 1.0.4 - resolution: "@shapeshiftoss/utils@npm:1.0.4" +"@shapeshiftoss/utils@npm:^1.0.6": + version: 1.0.6 + resolution: "@shapeshiftoss/utils@npm:1.0.6" dependencies: - "@shapeshiftoss/caip": "npm:^8.16.5" - "@shapeshiftoss/types": "npm:^8.6.5" + "@shapeshiftoss/caip": "npm:^8.16.8" + "@shapeshiftoss/types": "npm:^8.6.7" "@sniptt/monads": "npm:^0.5.10" bignumber.js: "npm:^9.3.1" dayjs: "npm:^1.11.3" - lodash: "npm:^4.17.21" - checksum: 10/fdaf59e78e7b6a9fce5bb4b24f4d9eb55e3ad41791b51fea9c6eb2040447bf239e6434c66051dc09f010f15ada26ff26debbc99a1a256a981a0b212e9bd452eb + lodash-es: "npm:^4.17.23" + checksum: 10/b1332a9176e1a3068e7879d05cc4681c791ee180bb9e056ea0055efdc79a6a680b26fecaf4f843276a64e48b72d19784fa5fcd78e9d00ecca7dbdf6b823ebf3c languageName: node linkType: hard @@ -5899,7 +6363,30 @@ __metadata: languageName: node linkType: hard -"@solana/web3.js@npm:^1.32.0, @solana/web3.js@npm:^1.68.0, @solana/web3.js@npm:^1.98.0": +"@solana/web3.js@npm:1.98.0": + version: 1.98.0 + resolution: "@solana/web3.js@npm:1.98.0" + dependencies: + "@babel/runtime": "npm:^7.25.0" + "@noble/curves": "npm:^1.4.2" + "@noble/hashes": "npm:^1.4.0" + "@solana/buffer-layout": "npm:^4.0.1" + agentkeepalive: "npm:^4.5.0" + bigint-buffer: "npm:^1.1.5" + bn.js: "npm:^5.2.1" + borsh: "npm:^0.7.0" + bs58: "npm:^4.0.1" + buffer: "npm:6.0.3" + fast-stable-stringify: "npm:^1.0.0" + jayson: "npm:^4.1.1" + node-fetch: "npm:^2.7.0" + rpc-websockets: "npm:^9.0.2" + superstruct: "npm:^2.0.2" + checksum: 10/b4d398c89a5007268b538b691b44c43b376dab59eb7e8acaa4c12a061812674a6e0b2802afb13e5f0ceacdb5102fb7bfb98d20743df0761f28b211aa37232b43 + languageName: node + linkType: hard + +"@solana/web3.js@npm:^1.32.0, @solana/web3.js@npm:^1.68.0": version: 1.98.4 resolution: "@solana/web3.js@npm:1.98.4" dependencies: @@ -5929,6 +6416,59 @@ __metadata: languageName: node linkType: hard +"@standard-schema/utils@npm:^0.3.0": + version: 0.3.0 + resolution: "@standard-schema/utils@npm:0.3.0" + checksum: 10/7084f875d322792f2e0a5904009434c8374b9345b09ba89828b68fd56fa3c2b366d35bf340d9e8c72736ef01793c2f70d350c372ed79845dc3566c58d34b4b51 + languageName: node + linkType: hard + +"@starknet-io/get-starknet-wallet-standard@npm:^5.0.0": + version: 5.0.0 + resolution: "@starknet-io/get-starknet-wallet-standard@npm:5.0.0" + dependencies: + "@starknet-io/types-js": "npm:^0.7.10" + "@wallet-standard/base": "npm:^1.1.0" + "@wallet-standard/features": "npm:^1.1.0" + ox: "npm:^0.4.4" + checksum: 10/3cba448993cd041875577b96611e0adaed5409898d77dbe5d6fe3580283dd59b48aff4a13c5efaf483ac5e7263caecdb56b102dba1bbbc7886e6a405c40b0c77 + languageName: node + linkType: hard + +"@starknet-io/starknet-types-010@npm:@starknet-io/types-js@0.10.0": + version: 0.10.0 + resolution: "@starknet-io/types-js@npm:0.10.0" + checksum: 10/04d033173ac53876154eb67d74602029199618537b3665ef67600dfeae1c2a3ec0cd9a486ff3d537403ba55acc63d6ae61760286364ad8d09ddbba4d1917ddd1 + languageName: node + linkType: hard + +"@starknet-io/starknet-types-09@npm:@starknet-io/types-js@~0.9.1": + version: 0.9.2 + resolution: "@starknet-io/types-js@npm:0.9.2" + checksum: 10/e71cc4f69c52e763273013801801fba33831c6bff9cc99fa065648647afba895944fc3b6ee438b004dac81367ccbf572396fd4c8b8f38ea16ecd838da87bd192 + languageName: node + linkType: hard + +"@starknet-io/types-js@npm:^0.7.10": + version: 0.7.10 + resolution: "@starknet-io/types-js@npm:0.7.10" + checksum: 10/e7e10878d6d576dcd30c6910a819e8e9cbd72102c71a93be7e0282f229d75c5765d2b5d152f7ae0693987cdb00e3d04f02bad371d91f420ddbbed435aadfbe3e + languageName: node + linkType: hard + +"@ston-fi/omniston-sdk@npm:^0.7.8": + version: 0.7.8 + resolution: "@ston-fi/omniston-sdk@npm:0.7.8" + dependencies: + isomorphic-ws: "npm:5.0.0" + json-rpc-2.0: "npm:1.7.0" + rxjs: "npm:7.8.1" + type-fest: "npm:5.0.1" + ws: "npm:8.17.1" + checksum: 10/08b953234e8356cb15cf70684be479e9bdea32ae23a591de8cbe73151b686d3024dd383f4b448aa2f2a214b8f7baec38a1eb19c5e8c8b002f580635e32a81647 + languageName: node + linkType: hard + "@swc/helpers@npm:^0.5.11": version: 0.5.17 resolution: "@swc/helpers@npm:0.5.17" @@ -5965,6 +6505,35 @@ __metadata: languageName: node linkType: hard +"@ton/core@npm:^0.62.1": + version: 0.62.1 + resolution: "@ton/core@npm:0.62.1" + peerDependencies: + "@ton/crypto": ">=3.2.0" + checksum: 10/f8c477a73f8b4f00451da7ab56c0aae328c6c49b94f01ebac3297da4977a2b315b5dd67da4ce78fc32e3f300e1e2e49e3b981d2de7d160244118856b5a90becf + languageName: node + linkType: hard + +"@ton/crypto-primitives@npm:2.1.0": + version: 2.1.0 + resolution: "@ton/crypto-primitives@npm:2.1.0" + dependencies: + jssha: "npm:3.2.0" + checksum: 10/71119f74461ae17bf2cfe7e0a6fcea8d4e359665ea6878b0c935cfd83ca0d84f9c299df3467adb1b1b7ba50f7d446732f2c13b5ea5e26dc1703a6dc24063be3a + languageName: node + linkType: hard + +"@ton/crypto@npm:^3.3.0": + version: 3.3.0 + resolution: "@ton/crypto@npm:3.3.0" + dependencies: + "@ton/crypto-primitives": "npm:2.1.0" + jssha: "npm:3.2.0" + tweetnacl: "npm:1.0.3" + checksum: 10/0561f2c95df7a53c47262393590bae8c5254ea5802cbecd2fea589073d926f60466292d594171aa3f3375ccaa2b763368fa1023bd5b6ebfe4da16297731cb379 + languageName: node + linkType: hard + "@tsconfig/node10@npm:^1.0.7": version: 1.0.11 resolution: "@tsconfig/node10@npm:1.0.11" @@ -6270,7 +6839,7 @@ __metadata: languageName: node linkType: hard -"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.15, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": +"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.11, @types/json-schema@npm:^7.0.15, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": version: 7.0.15 resolution: "@types/json-schema@npm:7.0.15" checksum: 10/1a3c3e06236e4c4aab89499c428d585527ce50c24fe8259e8b3926d3df4cfbbbcf306cfc73ddfb66cbafc973116efd15967020b0f738f63e09e64c7d260519e7 @@ -6932,6 +7501,22 @@ __metadata: languageName: node linkType: hard +"@wallet-standard/base@npm:^1.1.0": + version: 1.1.0 + resolution: "@wallet-standard/base@npm:1.1.0" + checksum: 10/11dbb8ed80566265916ab193ad5eab1585d55996781a88039d2bc4480428b1e778901b2dcff3e688dcac7de45e8a9272026f37f07f1e75168caff581906c5079 + languageName: node + linkType: hard + +"@wallet-standard/features@npm:^1.1.0": + version: 1.1.0 + resolution: "@wallet-standard/features@npm:1.1.0" + dependencies: + "@wallet-standard/base": "npm:^1.1.0" + checksum: 10/e046f813ec4bfea172aeb6c11358a962afe8f9a6961453e621d624f89d8b5fc8a44404dacfe18d33be815df6e9117bbf914009f5a9f9ea91ff90a136043fcac8 + languageName: node + linkType: hard + "@webassemblyjs/ast@npm:1.14.1, @webassemblyjs/ast@npm:^1.14.1": version: 1.14.1 resolution: "@webassemblyjs/ast@npm:1.14.1" @@ -7104,6 +7689,16 @@ __metadata: languageName: node linkType: hard +"@zodios/core@npm:^10.9.6": + version: 10.9.6 + resolution: "@zodios/core@npm:10.9.6" + peerDependencies: + axios: ^0.x || ^1.0.0 + zod: ^3.x + checksum: 10/3f7dfda9a423e2300bde79b4e7cf9bed2fea4639f7a742f88b06b88ae7d7e0cdef05171ba21ff3cf016ec6569a2b866517fd1a78c5346477f679c381d2526afd + languageName: node + linkType: hard + "abbrev@npm:^2.0.0": version: 2.0.0 resolution: "abbrev@npm:2.0.0" @@ -7118,24 +7713,23 @@ __metadata: languageName: node linkType: hard -"abitype@npm:1.1.0": - version: 1.1.0 - resolution: "abitype@npm:1.1.0" - peerDependencies: - typescript: ">=5.0.4" - zod: ^3.22.0 || ^4.0.0 - peerDependenciesMeta: - typescript: - optional: true - zod: - optional: true - checksum: 10/fe445c095dcb255e32c50bb1342a49d32c03def8549347bfe7f73f54ebdc3198adf2af6366af89e1e9bd3d04beab3f22f35e099754655a6becd45e09ca30d375 +"abi-wan-kanabi@npm:2.2.4": + version: 2.2.4 + resolution: "abi-wan-kanabi@npm:2.2.4" + dependencies: + ansicolors: "npm:^0.3.2" + cardinal: "npm:^2.1.1" + fs-extra: "npm:^10.0.0" + yargs: "npm:^17.7.2" + bin: + generate: dist/generate.js + checksum: 10/988c9c1cb926307c45ffc568d1b53c226bc986be8017e4f138a520c82ef5b72b3e762084bea2fa2730b3838d6956349a20c9fdcc6f494d5474e80214cc4cd00b languageName: node linkType: hard -"abitype@npm:^1.0.9": - version: 1.2.0 - resolution: "abitype@npm:1.2.0" +"abitype@npm:1.2.3, abitype@npm:^1.0.6, abitype@npm:^1.2.3": + version: 1.2.3 + resolution: "abitype@npm:1.2.3" peerDependencies: typescript: ">=5.0.4" zod: ^3.22.0 || ^4.0.0 @@ -7144,7 +7738,7 @@ __metadata: optional: true zod: optional: true - checksum: 10/04eb6bb8eec9422c538995cd508db851e6e59b84fb90e20207e43a6a1d1dcb4d434abacb7c5a277a0c039447dc666e676bbc1548e917e554c499ce86443416a5 + checksum: 10/94e744c2fc301b1cff59163a21b499aae0ddecdf4d3bef1579ff16b705e6f5738fd314125d791ed142487db2473d4fadcdbabb1e05e4b5d35715bc4ef35e400a languageName: node linkType: hard @@ -7262,9 +7856,9 @@ __metadata: languageName: node linkType: hard -"ajv-formats@npm:3.0.1": - version: 3.0.1 - resolution: "ajv-formats@npm:3.0.1" +"ajv-formats@npm:2.1.1, ajv-formats@npm:^2.1.1": + version: 2.1.1 + resolution: "ajv-formats@npm:2.1.1" dependencies: ajv: "npm:^8.0.0" peerDependencies: @@ -7272,13 +7866,13 @@ __metadata: peerDependenciesMeta: ajv: optional: true - checksum: 10/5679b9f9ced9d0213a202a37f3aa91efcffe59a6de1a6e3da5c873344d3c161820a1f11cc29899661fee36271fd2895dd3851b6461c902a752ad661d1c1e8722 + checksum: 10/70c263ded219bf277ffd9127f793b625f10a46113b2e901e150da41931fcfd7f5592da6d66862f4449bb157ffe65867c3294a7df1d661cc232c4163d5a1718ed languageName: node linkType: hard -"ajv-formats@npm:^2.1.1": - version: 2.1.1 - resolution: "ajv-formats@npm:2.1.1" +"ajv-formats@npm:3.0.1": + version: 3.0.1 + resolution: "ajv-formats@npm:3.0.1" dependencies: ajv: "npm:^8.0.0" peerDependencies: @@ -7286,7 +7880,7 @@ __metadata: peerDependenciesMeta: ajv: optional: true - checksum: 10/70c263ded219bf277ffd9127f793b625f10a46113b2e901e150da41931fcfd7f5592da6d66862f4449bb157ffe65867c3294a7df1d661cc232c4163d5a1718ed + checksum: 10/5679b9f9ced9d0213a202a37f3aa91efcffe59a6de1a6e3da5c873344d3c161820a1f11cc29899661fee36271fd2895dd3851b6461c902a752ad661d1c1e8722 languageName: node linkType: hard @@ -7310,6 +7904,18 @@ __metadata: languageName: node linkType: hard +"ajv@npm:8.11.2": + version: 8.11.2 + resolution: "ajv@npm:8.11.2" + dependencies: + fast-deep-equal: "npm:^3.1.1" + json-schema-traverse: "npm:^1.0.0" + require-from-string: "npm:^2.0.2" + uri-js: "npm:^4.2.2" + checksum: 10/6a68106196a30cd0159fc7309c8257ea41babc674b02febdc9848557a898faf7eeba9fa1c563788e058659c3f1aa5b8b565a5677f6e8d0b780b3c0bc828955b2 + languageName: node + linkType: hard + "ajv@npm:8.17.1, ajv@npm:^8.0.0, ajv@npm:^8.11.0, ajv@npm:^8.9.0": version: 8.17.1 resolution: "ajv@npm:8.17.1" @@ -7387,6 +7993,13 @@ __metadata: languageName: node linkType: hard +"ansicolors@npm:^0.3.2, ansicolors@npm:~0.3.2": + version: 0.3.2 + resolution: "ansicolors@npm:0.3.2" + checksum: 10/0704d1485d84d65a47aacd3d2d26f501f21aeeb509922c8f2496d0ec5d346dc948efa64f3151aef0571d73e5c44eb10fd02f27f59762e9292fe123bb1ea9ff7d + languageName: node + linkType: hard + "ansis@npm:4.1.0": version: 4.1.0 resolution: "ansis@npm:4.1.0" @@ -7527,16 +8140,18 @@ __metadata: languageName: node linkType: hard -"axios-cache-interceptor@npm:^1.5.3": - version: 1.8.3 - resolution: "axios-cache-interceptor@npm:1.8.3" +"axios-cache-interceptor@npm:^1.11.1": + version: 1.11.4 + resolution: "axios-cache-interceptor@npm:1.11.4" dependencies: - cache-parser: "npm:1.2.5" - fast-defer: "npm:1.1.8" - object-code: "npm:1.3.3" + cache-parser: "npm:^1.2.6" + fast-defer: "npm:^1.1.9" + http-vary: "npm:^1.0.3" + object-code: "npm:^2.0.0" + try: "npm:^1.0.3" peerDependencies: axios: ^1 - checksum: 10/1f6728009e6681b8f4c31beb1c1af60840c10ca49d4960cab891856717dcc9f6787c95a188226ac60c0cb1af687a2a0a8a8e50fec72e98b39a17c831535e5fc9 + checksum: 10/8710a4d7e76fa872921688020bf3783ad150471cfee07225cb8e5eb87f800a6ee7c07fa9e188e745385e814a251668fb568435799e8a8c364faa649b72445cb5 languageName: node linkType: hard @@ -7572,6 +8187,17 @@ __metadata: languageName: node linkType: hard +"axios@npm:1.13.2, axios@npm:^1.13.0, axios@npm:^1.6.2, axios@npm:^1.6.8, axios@npm:^1.7.4": + version: 1.13.2 + resolution: "axios@npm:1.13.2" + dependencies: + follow-redirects: "npm:^1.15.6" + form-data: "npm:^4.0.4" + proxy-from-env: "npm:^1.1.0" + checksum: 10/ae4e06dcd18289f2fd18179256d550d27f9a53ecb2f9c59f2ccc4efd1d7151839ba8c3e0fb533dac793e4a59a576ca8689a19244dce5c396680837674a47a867 + languageName: node + linkType: hard + "axios@npm:^0.21.2": version: 0.21.4 resolution: "axios@npm:0.21.4" @@ -7581,14 +8207,14 @@ __metadata: languageName: node linkType: hard -"axios@npm:^1.13.0, axios@npm:^1.6.2, axios@npm:^1.6.8, axios@npm:^1.7.4": - version: 1.13.2 - resolution: "axios@npm:1.13.2" +"axios@npm:^1.13.5": + version: 1.13.5 + resolution: "axios@npm:1.13.5" dependencies: - follow-redirects: "npm:^1.15.6" - form-data: "npm:^4.0.4" + follow-redirects: "npm:^1.15.11" + form-data: "npm:^4.0.5" proxy-from-env: "npm:^1.1.0" - checksum: 10/ae4e06dcd18289f2fd18179256d550d27f9a53ecb2f9c59f2ccc4efd1d7151839ba8c3e0fb533dac793e4a59a576ca8689a19244dce5c396680837674a47a867 + checksum: 10/db726d09902565ef9a0632893530028310e2ec2b95b727114eca1b101450b00014133dfc3871cffc87983fb922bca7e4874d7e2826d1550a377a157cdf3f05b6 languageName: node linkType: hard @@ -7675,6 +8301,15 @@ __metadata: languageName: node linkType: hard +"base-x@npm:^2.0.1": + version: 2.0.6 + resolution: "base-x@npm:2.0.6" + dependencies: + safe-buffer: "npm:^5.0.1" + checksum: 10/622357405a8ce44e39bfcd3266278279f8a21e06850303e422260fd7a776720c2e7cbb70e7804bef310f852fa604016b624e10b442da465e312d32ba908d753d + languageName: node + linkType: hard + "base-x@npm:^3.0.2": version: 3.0.11 resolution: "base-x@npm:3.0.11" @@ -7790,7 +8425,7 @@ __metadata: languageName: node linkType: hard -"bignumber.js@npm:^9.0.1, bignumber.js@npm:^9.1.1, bignumber.js@npm:^9.1.2, bignumber.js@npm:^9.3.1": +"bignumber.js@npm:^9.0.0, bignumber.js@npm:^9.0.1, bignumber.js@npm:^9.1.1, bignumber.js@npm:^9.1.2, bignumber.js@npm:^9.3.1": version: 9.3.1 resolution: "bignumber.js@npm:9.3.1" checksum: 10/1be0372bf0d6d29d0a49b9e6a9cefbd54dad9918232ad21fcd4ec39030260773abf0c76af960c6b3b98d3115a3a71e61c6a111812d1395040a039cfa178e0245 @@ -7832,7 +8467,7 @@ __metadata: languageName: node linkType: hard -"bip174@npm:^3.0.0-rc.0": +"bip174@npm:^3.0.0, bip174@npm:^3.0.0-rc.0": version: 3.0.0 resolution: "bip174@npm:3.0.0" dependencies: @@ -7864,6 +8499,15 @@ __metadata: languageName: node linkType: hard +"bip39@npm:^3.1.0": + version: 3.1.0 + resolution: "bip39@npm:3.1.0" + dependencies: + "@noble/hashes": "npm:^1.2.0" + checksum: 10/406c0b5bdab0d43df2ff8916c5b57bb7f65cd36a115cbd7620a75b972638f8511840bfc27bfc68946027086fd33ed3050d8cd304e85fd527503b23d857a8486e + languageName: node + linkType: hard + "bip66@npm:^1.1.0, bip66@npm:^1.1.5": version: 1.1.5 resolution: "bip66@npm:1.1.5" @@ -7946,6 +8590,13 @@ __metadata: languageName: node linkType: hard +"bn.js@npm:5.2.1": + version: 5.2.1 + resolution: "bn.js@npm:5.2.1" + checksum: 10/7a7e8764d7a6e9708b8b9841b2b3d6019cc154d2fc23716d0efecfe1e16921b7533c6f7361fb05471eab47986c4aa310c270f88e3507172104632ac8df2cfd84 + languageName: node + linkType: hard + "bn.js@npm:^4.11.0, bn.js@npm:^4.11.8, bn.js@npm:^4.11.9": version: 4.12.2 resolution: "bn.js@npm:4.12.2" @@ -7977,6 +8628,13 @@ __metadata: languageName: node linkType: hard +"borsh@npm:1.0.0": + version: 1.0.0 + resolution: "borsh@npm:1.0.0" + checksum: 10/7ee685dda8d1d77051869cb320c13e95291e4160cf0d833a66a7d24d45d7f5603caa56a8d4d0e032e4c9201f714dd32f77c6adfb1f3e2b200f46886001184889 + languageName: node + linkType: hard + "borsh@npm:^0.7.0": version: 0.7.0 resolution: "borsh@npm:0.7.0" @@ -8075,6 +8733,15 @@ __metadata: languageName: node linkType: hard +"bs58@npm:4.0.0": + version: 4.0.0 + resolution: "bs58@npm:4.0.0" + dependencies: + base-x: "npm:^2.0.1" + checksum: 10/af0f18e05d7612617453b5bb5f3913f5c1b804fbda9176c9008f38df6eb3556a4abdba5367fff692cbec19c5cd7c59bfe7d70885bccf8d6ea5a277b3230025d2 + languageName: node + linkType: hard + "bs58@npm:^4.0.0, bs58@npm:^4.0.1": version: 4.0.1 resolution: "bs58@npm:4.0.1" @@ -8093,7 +8760,7 @@ __metadata: languageName: node linkType: hard -"bs58check@npm:2.1.2, bs58check@npm:<3.0.0, bs58check@npm:^2.0.0, bs58check@npm:^2.1.1, bs58check@npm:^2.1.2": +"bs58check@npm:<3.0.0, bs58check@npm:^2.0.0, bs58check@npm:^2.1.1, bs58check@npm:^2.1.2": version: 2.1.2 resolution: "bs58check@npm:2.1.2" dependencies: @@ -8268,10 +8935,10 @@ __metadata: languageName: node linkType: hard -"cache-parser@npm:1.2.5": - version: 1.2.5 - resolution: "cache-parser@npm:1.2.5" - checksum: 10/c6c1600433c70ce403b5f63d47463c0aac88b033ca4a7dae155b300e1246546fe68c840661762b6e1c3a16b8ba079fc16ca1eb834dfc929e06539db9af0be3cb +"cache-parser@npm:^1.2.6": + version: 1.2.6 + resolution: "cache-parser@npm:1.2.6" + checksum: 10/e40ab0d8677d58873c71cd602750a8d85566104b9b45d6e216125009a27f28c3bd04bdeb4bb3a0afa48da401197e6dd6aa291e9b71493b8c05b3e90154dc98f5 languageName: node linkType: hard @@ -8368,6 +9035,18 @@ __metadata: languageName: node linkType: hard +"cardinal@npm:^2.1.1": + version: 2.1.1 + resolution: "cardinal@npm:2.1.1" + dependencies: + ansicolors: "npm:~0.3.2" + redeyed: "npm:~2.1.0" + bin: + cdl: ./bin/cdl.js + checksum: 10/caf0d34739ef7b1d80e1753311f889997b62c4490906819eb5da5bd46e7f5e5caba7a8a96ca401190c7d9c18443a7749e5338630f7f9a1ae98d60cac49b9008e + languageName: node + linkType: hard + "cashaddrjs@npm:^0.3.12": version: 0.3.12 resolution: "cashaddrjs@npm:0.3.12" @@ -8917,6 +9596,13 @@ __metadata: languageName: node linkType: hard +"data-uri-to-buffer@npm:^4.0.0": + version: 4.0.1 + resolution: "data-uri-to-buffer@npm:4.0.1" + checksum: 10/0d0790b67ffec5302f204c2ccca4494f70b4e2d940fea3d36b09f0bb2b8539c2e86690429eb1f1dc4bcc9e4df0644193073e63d9ee48ac9fce79ec1506e4aa4c + languageName: node + linkType: hard + "data-view-buffer@npm:^1.0.2": version: 1.0.2 resolution: "data-view-buffer@npm:1.0.2" @@ -8950,7 +9636,7 @@ __metadata: languageName: node linkType: hard -"dayjs@npm:^1.11.3": +"dayjs@npm:^1.11.19, dayjs@npm:^1.11.3": version: 1.11.19 resolution: "dayjs@npm:1.11.19" checksum: 10/185b820d68492b83a3ce2b8ddc7543034edc1dfd1423183f6ae4707b29929a3cc56503a81826309279f9084680c15966b99456e74cf41f7d1f6a2f98f9c7196f @@ -9118,13 +9804,20 @@ __metadata: languageName: node linkType: hard -"depd@npm:^2.0.0, depd@npm:~2.0.0": +"depd@npm:2.0.0, depd@npm:^2.0.0, depd@npm:~2.0.0": version: 2.0.0 resolution: "depd@npm:2.0.0" checksum: 10/c0c8ff36079ce5ada64f46cc9d6fd47ebcf38241105b6e0c98f412e8ad91f084bcf906ff644cc3a4bd876ca27a62accb8b0fff72ea6ed1a414b89d8506f4a5ca languageName: node linkType: hard +"depd@npm:~1.1.2": + version: 1.1.2 + resolution: "depd@npm:1.1.2" + checksum: 10/2ed6966fc14463a9e85451db330ab8ba041efed0b9a1a472dbfc6fbf2f82bab66491915f996b25d8517dddc36c8c74e24c30879b34877f3c4410733444a51d1d + languageName: node + linkType: hard + "destr@npm:^2.0.3": version: 2.0.5 resolution: "destr@npm:2.0.5" @@ -9747,7 +10440,7 @@ __metadata: languageName: node linkType: hard -"esprima@npm:^4.0.0, esprima@npm:^4.0.1": +"esprima@npm:^4.0.0, esprima@npm:^4.0.1, esprima@npm:~4.0.0": version: 4.0.1 resolution: "esprima@npm:4.0.1" bin: @@ -10029,6 +10722,22 @@ __metadata: languageName: node linkType: hard +"eventsource-parser@npm:^3.0.1": + version: 3.0.6 + resolution: "eventsource-parser@npm:3.0.6" + checksum: 10/febf7058b9c2168ecbb33e92711a1646e06bd1568f60b6eb6a01a8bf9f8fcd29cc8320d57247059cacf657a296280159f21306d2e3ff33309a9552b2ef889387 + languageName: node + linkType: hard + +"eventsource@npm:^3.0.5": + version: 3.0.7 + resolution: "eventsource@npm:3.0.7" + dependencies: + eventsource-parser: "npm:^3.0.1" + checksum: 10/e034915bc97068d1d38617951afd798e6776d6a3a78e36a7569c235b177c7afc2625c9fe82656f7341ab72c7eeecb3fd507b7f88e9328f2448872ff9c4742bb6 + languageName: node + linkType: hard + "evp_bytestokey@npm:^1.0.3": version: 1.0.3 resolution: "evp_bytestokey@npm:1.0.3" @@ -10089,7 +10798,7 @@ __metadata: languageName: node linkType: hard -"exponential-backoff@npm:^3.1.1": +"exponential-backoff@npm:^3.1.1, exponential-backoff@npm:^3.1.2": version: 3.1.3 resolution: "exponential-backoff@npm:3.1.3" checksum: 10/ca25962b4bbab943b7c4ed0b5228e263833a5063c65e1cdeac4be9afad350aae5466e8e619b5051f4f8d37b2144a2d6e8fcc771b6cc82934f7dade2f964f652c @@ -10161,10 +10870,10 @@ __metadata: languageName: node linkType: hard -"fast-defer@npm:1.1.8": - version: 1.1.8 - resolution: "fast-defer@npm:1.1.8" - checksum: 10/2fbdcf385929b53a2299c83cfb9c0ac4c0f0c4d1c268e791ed8a97dad9c25a7f459e1475e8270f268d29541be0a8b14a764d77f18cc7caea95c1863cf87c471b +"fast-defer@npm:^1.1.9": + version: 1.1.9 + resolution: "fast-defer@npm:1.1.9" + checksum: 10/9aea55eccc52046e95d430d0dabae1b49ead18ad5d911c9e3a504052086b54845d9b53ec2b4291831f06dbebd3223d2c12f98ad3242dd7dbd2894c4c8fbb47f8 languageName: node linkType: hard @@ -10271,6 +10980,16 @@ __metadata: languageName: node linkType: hard +"fetch-blob@npm:^3.1.2, fetch-blob@npm:^3.1.4": + version: 3.2.0 + resolution: "fetch-blob@npm:3.2.0" + dependencies: + node-domexception: "npm:^1.0.0" + web-streams-polyfill: "npm:^3.0.3" + checksum: 10/5264ecceb5fdc19eb51d1d0359921f12730941e333019e673e71eb73921146dceabcb0b8f534582be4497312d656508a439ad0f5edeec2b29ab2e10c72a1f86b + languageName: node + linkType: hard + "fflate@npm:^0.8.2": version: 0.8.2 resolution: "fflate@npm:0.8.2" @@ -10395,7 +11114,7 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.14.0, follow-redirects@npm:^1.14.9, follow-redirects@npm:^1.15.6": +"follow-redirects@npm:^1.14.0, follow-redirects@npm:^1.14.9, follow-redirects@npm:^1.15.11, follow-redirects@npm:^1.15.6": version: 1.15.11 resolution: "follow-redirects@npm:1.15.11" peerDependenciesMeta: @@ -10447,7 +11166,7 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^4.0.0": +"form-data@npm:^4.0.0, form-data@npm:^4.0.5": version: 4.0.5 resolution: "form-data@npm:4.0.5" dependencies: @@ -10473,6 +11192,15 @@ __metadata: languageName: node linkType: hard +"formdata-polyfill@npm:^4.0.10": + version: 4.0.10 + resolution: "formdata-polyfill@npm:4.0.10" + dependencies: + fetch-blob: "npm:^3.1.2" + checksum: 10/9b5001d2edef3c9449ac3f48bd4f8cc92e7d0f2e7c1a5c8ba555ad4e77535cc5cf621fabe49e97f304067037282dd9093b9160a3cb533e46420b446c4e6bc06f + languageName: node + linkType: hard + "forwarded@npm:0.2.0": version: 0.2.0 resolution: "forwarded@npm:0.2.0" @@ -11047,6 +11775,19 @@ __metadata: languageName: node linkType: hard +"http-errors@npm:1.7.2": + version: 1.7.2 + resolution: "http-errors@npm:1.7.2" + dependencies: + depd: "npm:~1.1.2" + inherits: "npm:2.0.3" + setprototypeof: "npm:1.1.1" + statuses: "npm:>= 1.5.0 < 2" + toidentifier: "npm:1.0.0" + checksum: 10/cf8da344b181599d19a2bfedcbe7c946945a907f2825a0c89e119ce9f9c9a421a49898afe3291485b40ffbbd587b62326f9becc7aa053036eff2559d9436defb + languageName: node + linkType: hard + "http-errors@npm:^2.0.0, http-errors@npm:~2.0.1": version: 2.0.1 resolution: "http-errors@npm:2.0.1" @@ -11070,6 +11811,13 @@ __metadata: languageName: node linkType: hard +"http-vary@npm:^1.0.3": + version: 1.0.3 + resolution: "http-vary@npm:1.0.3" + checksum: 10/af13bb1a56b2a0845befd8a6dae656393d7dda01780d0ce73b6887b9925640d92093a6e8e984314c2203c2f37c16f34b56f6b2032bcb254d51f40fa37940429c + languageName: node + linkType: hard + "http2-wrapper@npm:^1.0.0-beta.5.2": version: 1.0.3 resolution: "http2-wrapper@npm:1.0.3" @@ -11164,6 +11912,13 @@ __metadata: languageName: node linkType: hard +"immer@npm:^11.0.0": + version: 11.1.4 + resolution: "immer@npm:11.1.4" + checksum: 10/7b60a56f8375bbe1b3fdbeec322614ab7925dde6480cc5da66a5204f44ccbce522b2b2ccda3bef3a9e71e5746b28dcf4ae174443a49df1155e983e88b0aa0ed5 + languageName: node + linkType: hard + "import-fresh@npm:^3.2.1, import-fresh@npm:^3.3.0": version: 3.3.1 resolution: "import-fresh@npm:3.3.1" @@ -11236,6 +11991,13 @@ __metadata: languageName: node linkType: hard +"inherits@npm:2.0.3": + version: 2.0.3 + resolution: "inherits@npm:2.0.3" + checksum: 10/8771303d66c51be433b564427c16011a8e3fbc3449f1f11ea50efb30a4369495f1d0e89f0fc12bdec0bd7e49102ced5d137e031d39ea09821cb3c717fcf21e69 + languageName: node + linkType: hard + "ini@npm:^2.0.0": version: 2.0.0 resolution: "ini@npm:2.0.0" @@ -11711,6 +12473,15 @@ __metadata: languageName: node linkType: hard +"isomorphic-ws@npm:5.0.0": + version: 5.0.0 + resolution: "isomorphic-ws@npm:5.0.0" + peerDependencies: + ws: "*" + checksum: 10/e20eb2aee09ba96247465fda40c6d22c1153394c0144fa34fe6609f341af4c8c564f60ea3ba762335a7a9c306809349f9b863c8beedf2beea09b299834ad5398 + languageName: node + linkType: hard + "isomorphic-ws@npm:^4.0.1": version: 4.0.1 resolution: "isomorphic-ws@npm:4.0.1" @@ -12409,6 +13180,15 @@ __metadata: languageName: node linkType: hard +"json-bigint@npm:^1.0.0": + version: 1.0.0 + resolution: "json-bigint@npm:1.0.0" + dependencies: + bignumber.js: "npm:^9.0.0" + checksum: 10/cd3973b88e5706f8f89d2a9c9431f206ef385bd5c584db1b258891a5e6642507c32316b82745239088c697f5ddfe967351e1731f5789ba7855aed56ad5f70e1f + languageName: node + linkType: hard + "json-buffer@npm:3.0.1": version: 3.0.1 resolution: "json-buffer@npm:3.0.1" @@ -12430,6 +13210,13 @@ __metadata: languageName: node linkType: hard +"json-rpc-2.0@npm:1.7.0": + version: 1.7.0 + resolution: "json-rpc-2.0@npm:1.7.0" + checksum: 10/063e6e19fd41bd96e2b125b70e153961bc244c03e139ff7d64caa548b35986fb537f72f4ca9cfe45c43bbb707861cf08983f1b1c61027432128c22f939080e0c + languageName: node + linkType: hard + "json-schema-traverse@npm:^0.4.1": version: 0.4.1 resolution: "json-schema-traverse@npm:0.4.1" @@ -12520,6 +13307,13 @@ __metadata: languageName: node linkType: hard +"jssha@npm:3.2.0": + version: 3.2.0 + resolution: "jssha@npm:3.2.0" + checksum: 10/6d01e8fa96a05534f19f81f7311f3f925d456007fd772085acada24f6013573c2854650d6b23c193afe18ba06efb40b93ce3ea6c45797ae4f79c0afcfaf6def0 + languageName: node + linkType: hard + "jssha@npm:^3.3.1": version: 3.3.1 resolution: "jssha@npm:3.3.1" @@ -12650,6 +13444,13 @@ __metadata: languageName: node linkType: hard +"lodash-es@npm:^4.17.23": + version: 4.17.23 + resolution: "lodash-es@npm:4.17.23" + checksum: 10/1feae200df22eb0bd93ca86d485e77784b8a9fb1d13e91b66e9baa7a7e5e04be088c12a7e20c2250fc0bd3db1bc0ef0affc7d9e3810b6af2455a3c6bf6dde59e + languageName: node + linkType: hard + "lodash.camelcase@npm:^4.3.0": version: 4.3.0 resolution: "lodash.camelcase@npm:4.3.0" @@ -12685,6 +13486,13 @@ __metadata: languageName: node linkType: hard +"lodash@npm:^4.17.23": + version: 4.17.23 + resolution: "lodash@npm:4.17.23" + checksum: 10/82504c88250f58da7a5a4289f57a4f759c44946c005dd232821c7688b5fcfbf4a6268f6a6cdde4b792c91edd2f3b5398c1d2a0998274432cff76def48735e233 + languageName: node + linkType: hard + "log-symbols@npm:^4.1.0": version: 4.1.0 resolution: "log-symbols@npm:4.1.0" @@ -12720,6 +13528,13 @@ __metadata: languageName: node linkType: hard +"lossless-json@npm:^4.2.0": + version: 4.3.0 + resolution: "lossless-json@npm:4.3.0" + checksum: 10/a984a882c79b6e62a917d0202518472c17587bdee002f1427d7ec61115f9fbdeb5f0baa9f0e9fff8d9dbacd17da6b6c910012b2dcab69dd33d151cb7c13d5a37 + languageName: node + linkType: hard + "lower-case@npm:^2.0.2": version: 2.0.2 resolution: "lower-case@npm:2.0.2" @@ -12775,6 +13590,13 @@ __metadata: languageName: node linkType: hard +"lru_map@npm:0.4.1": + version: 0.4.1 + resolution: "lru_map@npm:0.4.1" + checksum: 10/c399afdb959c1820ace33b253c307c00e39a8a63705037f9dd58bf489950265de347b1614642fcfde26e0ff613fba633ddfd7eb5814ffb0a360a7037cc8a4008 + languageName: node + linkType: hard + "luxon@npm:~3.7.0": version: 3.7.2 resolution: "luxon@npm:3.7.2" @@ -13280,6 +14102,13 @@ __metadata: languageName: node linkType: hard +"moment@npm:^2.30.1": + version: 2.30.1 + resolution: "moment@npm:2.30.1" + checksum: 10/ae42d876d4ec831ef66110bdc302c0657c664991e45cf2afffc4b0f6cd6d251dde11375c982a5c0564ccc0fa593fc564576ddceb8c8845e87c15f58aa6baca69 + languageName: node + linkType: hard + "ms@npm:^2.0.0, ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" @@ -13377,6 +14206,15 @@ __metadata: languageName: node linkType: hard +"mustache@npm:4.0.0": + version: 4.0.0 + resolution: "mustache@npm:4.0.0" + bin: + mustache: ./bin/mustache + checksum: 10/7e37973a82db7ccc806eb0a7cf4dacc7c7cc27340425f3a76106c9893e1379469dbd972e8f153878891743a07b51aed1c73c88ffa41230bd1a68bd18a8f67061 + languageName: node + linkType: hard + "mute-stream@npm:^3.0.0": version: 3.0.0 resolution: "mute-stream@npm:3.0.0" @@ -13418,6 +14256,43 @@ __metadata: languageName: node linkType: hard +"near-abi@npm:0.1.1": + version: 0.1.1 + resolution: "near-abi@npm:0.1.1" + dependencies: + "@types/json-schema": "npm:^7.0.11" + checksum: 10/25b01b50b9a820250ad3ccf88164af242e1bbb895f1d14836b66d518fb3c016a66929959c1e3dda8ec2e2a8def23fc15843b8343a3b245da93727d86f8282425 + languageName: node + linkType: hard + +"near-api-js@npm:^3.0.2": + version: 3.0.4 + resolution: "near-api-js@npm:3.0.4" + dependencies: + "@near-js/accounts": "npm:1.0.4" + "@near-js/crypto": "npm:1.2.1" + "@near-js/keystores": "npm:0.0.9" + "@near-js/keystores-browser": "npm:0.0.9" + "@near-js/keystores-node": "npm:0.0.9" + "@near-js/providers": "npm:0.1.1" + "@near-js/signers": "npm:0.1.1" + "@near-js/transactions": "npm:1.1.2" + "@near-js/types": "npm:0.0.4" + "@near-js/utils": "npm:0.1.0" + "@near-js/wallet-account": "npm:1.1.1" + "@noble/curves": "npm:1.2.0" + ajv: "npm:8.11.2" + ajv-formats: "npm:2.1.1" + bn.js: "npm:5.2.1" + borsh: "npm:1.0.0" + depd: "npm:2.0.0" + http-errors: "npm:1.7.2" + near-abi: "npm:0.1.1" + node-fetch: "npm:2.6.7" + checksum: 10/6b70137c8c95e9564c81756798f13784bbd9ab4b04c9a485bef5241b25ee67e0d415a6ee423f4507025084997c5b56bda993a065255cd98fbd11683e5e56009e + languageName: node + linkType: hard + "negotiator@npm:0.6.3": version: 0.6.3 resolution: "negotiator@npm:0.6.3" @@ -13488,6 +14363,13 @@ __metadata: languageName: node linkType: hard +"node-domexception@npm:^1.0.0": + version: 1.0.0 + resolution: "node-domexception@npm:1.0.0" + checksum: 10/e332522f242348c511640c25a6fc7da4f30e09e580c70c6b13cb0be83c78c3e71c8d4665af2527e869fc96848924a4316ae7ec9014c091e2156f41739d4fa233 + languageName: node + linkType: hard + "node-emoji@npm:1.11.0": version: 1.11.0 resolution: "node-emoji@npm:1.11.0" @@ -13504,6 +14386,20 @@ __metadata: languageName: node linkType: hard +"node-fetch@npm:2.6.7": + version: 2.6.7 + resolution: "node-fetch@npm:2.6.7" + dependencies: + whatwg-url: "npm:^5.0.0" + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: 10/4bc9245383db92c35601a798c9a992fdf38d99920ceac11e0e6512ef3014d188b3807ccb060bc6c4bdb57a145030c73f5b5fd6730f665979f9264bc43ca3afea + languageName: node + linkType: hard + "node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.7.0": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" @@ -13518,6 +14414,17 @@ __metadata: languageName: node linkType: hard +"node-fetch@npm:^3.3.2": + version: 3.3.2 + resolution: "node-fetch@npm:3.3.2" + dependencies: + data-uri-to-buffer: "npm:^4.0.0" + fetch-blob: "npm:^3.1.4" + formdata-polyfill: "npm:^4.0.10" + checksum: 10/24207ca8c81231c7c59151840e3fded461d67a31cf3e3b3968e12201a42f89ce4a0b5fb7079b1fa0a4655957b1ca9257553200f03a9f668b45ebad265ca5593d + languageName: node + linkType: hard + "node-gyp-build@npm:^4.2.0, node-gyp-build@npm:^4.3.0": version: 4.8.4 resolution: "node-gyp-build@npm:4.8.4" @@ -13770,10 +14677,10 @@ __metadata: languageName: node linkType: hard -"object-code@npm:1.3.3": - version: 1.3.3 - resolution: "object-code@npm:1.3.3" - checksum: 10/f6413d2097556a2f0643d145ce57ced10025e381927bb5688d32757424ef674463e3487bd17cfff45e2efe378419a30a364fc46f2f96c29ae3d9c8f6f573ca52 +"object-code@npm:^2.0.0": + version: 2.0.0 + resolution: "object-code@npm:2.0.0" + checksum: 10/fc70faec7cc2370e64df1373fae146f061881d106d94eceb26fc41e64dbb96cb60822db266fc3f0f3e999592816f44b6398029f7c048ed68b7b8d14550096c0c languageName: node linkType: hard @@ -13916,9 +14823,9 @@ __metadata: languageName: node linkType: hard -"ox@npm:0.9.6": - version: 0.9.6 - resolution: "ox@npm:0.9.6" +"ox@npm:0.11.1": + version: 0.11.1 + resolution: "ox@npm:0.11.1" dependencies: "@adraffy/ens-normalize": "npm:^1.11.0" "@noble/ciphers": "npm:^1.3.0" @@ -13926,14 +14833,34 @@ __metadata: "@noble/hashes": "npm:^1.8.0" "@scure/bip32": "npm:^1.7.0" "@scure/bip39": "npm:^1.6.0" - abitype: "npm:^1.0.9" + abitype: "npm:^1.2.3" + eventemitter3: "npm:5.0.1" + peerDependencies: + typescript: ">=5.4.0" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10/7004d7d704bc8004d116d5527610dc69c8de775c67669ec2775fc7be4d98d3ed22fb012ae890c73f4e0b34fd96dee654387f7b65e1bfd8c05b05de761c57e207 + languageName: node + linkType: hard + +"ox@npm:^0.4.4": + version: 0.4.4 + resolution: "ox@npm:0.4.4" + dependencies: + "@adraffy/ens-normalize": "npm:^1.10.1" + "@noble/curves": "npm:^1.6.0" + "@noble/hashes": "npm:^1.5.0" + "@scure/bip32": "npm:^1.5.0" + "@scure/bip39": "npm:^1.4.0" + abitype: "npm:^1.0.6" eventemitter3: "npm:5.0.1" peerDependencies: typescript: ">=5.4.0" peerDependenciesMeta: typescript: optional: true - checksum: 10/9fb1a89c710c02366fe2fdda7db4e17474336456d3b80fa24e059b3d269b45e94abfe950d7f52b220abc33cde2b75d03d24f31493382a6bab37ec5aada2bd925 + checksum: 10/7e6624a91b2064c160af1bde0febb8044a35a4353885afa1cf2993f4755cad5f485857469393455a4fce3f4bc0e95f18b3dd34d5f59fd07c25fcc7ddb3047bf4 languageName: node linkType: hard @@ -14079,7 +15006,7 @@ __metadata: languageName: node linkType: hard -"pako@npm:^2.0.3": +"pako@npm:^2.0.3, pako@npm:^2.0.4": version: 2.1.0 resolution: "pako@npm:2.1.0" checksum: 10/38a04991d0ec4f4b92794a68b8c92bf7340692c5d980255c92148da96eb3e550df7a86a7128b5ac0c65ecddfe5ef3bbe9c6dab13e1bc315086e759b18f7c1401 @@ -14611,7 +15538,7 @@ __metadata: languageName: node linkType: hard -"qs@npm:^6.10.5, qs@npm:^6.14.0": +"qs@npm:^6.14.0": version: 6.14.0 resolution: "qs@npm:6.14.0" dependencies: @@ -14620,6 +15547,15 @@ __metadata: languageName: node linkType: hard +"qs@npm:^6.14.2": + version: 6.15.0 + resolution: "qs@npm:6.15.0" + dependencies: + side-channel: "npm:^1.1.0" + checksum: 10/a3458f2f389285c3512e0ebc55522ee370ac7cb720ba9f0eff3e30fb2bb07631caf556c08e2a3d4481a371ac14faa9ceb7442a0610c5a7e55b23a5bdee7b701c + languageName: node + linkType: hard + "queue-microtask@npm:^1.2.2": version: 1.2.3 resolution: "queue-microtask@npm:1.2.3" @@ -14657,7 +15593,7 @@ __metadata: languageName: node linkType: hard -"randombytes@npm:^2.0.1, randombytes@npm:^2.1.0": +"randombytes@npm:2.1.0, randombytes@npm:^2.0.1, randombytes@npm:^2.1.0": version: 2.1.0 resolution: "randombytes@npm:2.1.0" dependencies: @@ -14846,6 +15782,31 @@ __metadata: languageName: node linkType: hard +"redeyed@npm:~2.1.0": + version: 2.1.1 + resolution: "redeyed@npm:2.1.1" + dependencies: + esprima: "npm:~4.0.0" + checksum: 10/86880f97d54bb55bbf1c338e27fe28f18f52afc2f5afa808354a09a3777aa79b4f04e04844350d7fec80aa2d299196bde256b21f586e7e5d9b63494bd4a9db27 + languageName: node + linkType: hard + +"redux-thunk@npm:^3.1.0": + version: 3.1.0 + resolution: "redux-thunk@npm:3.1.0" + peerDependencies: + redux: ^5.0.0 + checksum: 10/38c563db5f0bbec90d2e65cc27f3c870c1b6102e0c071258734fac41cb0e51d31d894125815c2f4133b20aff231f51f028ad99bccc05a7e3249f1a5d5a959ed3 + languageName: node + linkType: hard + +"redux@npm:^5.0.1": + version: 5.0.1 + resolution: "redux@npm:5.0.1" + checksum: 10/a373f9ed65693ead58bea5ef61c1d6bef39da9f2706db3be6f84815f3a1283230ecd1184efb1b3daa7f807d8211b0181564ca8f336fc6ee0b1e2fa0ba06737c2 + languageName: node + linkType: hard + "reflect-metadata@npm:^0.1.13": version: 0.1.14 resolution: "reflect-metadata@npm:0.1.14" @@ -14922,6 +15883,13 @@ __metadata: languageName: node linkType: hard +"reselect@npm:^5.1.0": + version: 5.1.1 + resolution: "reselect@npm:5.1.1" + checksum: 10/1fdae11a39ed9c8d85a24df19517c8372ee24fefea9cce3fae9eaad8e9cefbba5a3d4940c6fe31296b6addf76e035588c55798f7e6e147e1b7c0855f119e7fa5 + languageName: node + linkType: hard + "resolve-alpn@npm:^1.0.0": version: 1.2.1 resolution: "resolve-alpn@npm:1.2.1" @@ -15094,6 +16062,15 @@ __metadata: languageName: node linkType: hard +"rxjs@npm:7.8.2, rxjs@npm:^7.8.1": + version: 7.8.2 + resolution: "rxjs@npm:7.8.2" + dependencies: + tslib: "npm:^2.1.0" + checksum: 10/03dff09191356b2b87d94fbc1e97c4e9eb3c09d4452399dddd451b09c2f1ba8d56925a40af114282d7bc0c6fe7514a2236ca09f903cf70e4bbf156650dddb49d + languageName: node + linkType: hard + "rxjs@npm:^6.4.0": version: 6.6.7 resolution: "rxjs@npm:6.6.7" @@ -15103,15 +16080,6 @@ __metadata: languageName: node linkType: hard -"rxjs@npm:^7.8.1": - version: 7.8.2 - resolution: "rxjs@npm:7.8.2" - dependencies: - tslib: "npm:^2.1.0" - checksum: 10/03dff09191356b2b87d94fbc1e97c4e9eb3c09d4452399dddd451b09c2f1ba8d56925a40af114282d7bc0c6fe7514a2236ca09f903cf70e4bbf156650dddb49d - languageName: node - linkType: hard - "safe-array-concat@npm:^1.1.3": version: 1.1.3 resolution: "safe-array-concat@npm:1.1.3" @@ -15206,6 +16174,18 @@ __metadata: languageName: node linkType: hard +"secp256k1@npm:5.0.1": + version: 5.0.1 + resolution: "secp256k1@npm:5.0.1" + dependencies: + elliptic: "npm:^6.5.7" + node-addon-api: "npm:^5.0.0" + node-gyp: "npm:latest" + node-gyp-build: "npm:^4.2.0" + checksum: 10/63fbd35624be4fd9cf3d39e5f79c5471b4a8aea6944453b2bea7b100bb1c77a25c55e6e08e2210cdabdf478c4c62d34c408b34214f2afd9367e19a52a3a4236c + languageName: node + linkType: hard + "secp256k1@npm:^3.0.1": version: 3.8.1 resolution: "secp256k1@npm:3.8.1" @@ -15362,6 +16342,13 @@ __metadata: languageName: node linkType: hard +"setprototypeof@npm:1.1.1": + version: 1.1.1 + resolution: "setprototypeof@npm:1.1.1" + checksum: 10/b8fcf5b4b8325ea638712ed6e62f8e0ffac69eef1390305a5331046992424e484d4d6603a18d84d4c08c3def50b9195d9e707b747aed5eec15ee66a2a6508318 + languageName: node + linkType: hard + "setprototypeof@npm:~1.2.0": version: 1.2.0 resolution: "setprototypeof@npm:1.2.0" @@ -15369,7 +16356,7 @@ __metadata: languageName: node linkType: hard -"sha.js@npm:^2.4.0, sha.js@npm:^2.4.11, sha.js@npm:^2.4.12, sha.js@npm:^2.4.8": +"sha.js@npm:^2.4.0, sha.js@npm:^2.4.12, sha.js@npm:^2.4.8": version: 2.4.12 resolution: "sha.js@npm:2.4.12" dependencies: @@ -15387,6 +16374,7 @@ __metadata: resolution: "shapeshift-backend@workspace:." dependencies: "@bitcoinerlab/secp256k1": "npm:^1.1.1" + "@cetusprotocol/aggregator-sdk": "npm:^1.4.5" "@defuse-protocol/one-click-sdk-typescript": "npm:^0.1.1-0.2" "@eslint/eslintrc": "npm:^3.2.0" "@eslint/js": "npm:^9.18.0" @@ -15405,10 +16393,10 @@ __metadata: "@pulumi/kubernetes": "npm:4.23.0" "@pulumi/pulumi": "npm:3.160.0" "@shapeshiftoss/caip": "npm:8.16.7" - "@shapeshiftoss/chain-adapters": "npm:11.3.6" - "@shapeshiftoss/swapper": "npm:17.6.7" - "@shapeshiftoss/types": "npm:8.6.5" - "@shapeshiftoss/unchained-client": "npm:10.14.8" + "@shapeshiftoss/chain-adapters": "npm:11.3.9" + "@shapeshiftoss/swapper": "npm:17.6.10" + "@shapeshiftoss/types": "npm:8.6.7" + "@shapeshiftoss/unchained-client": "npm:10.14.10" "@shapeshiftoss/unchained-pulumi": "npm:1.0.2" "@types/express": "npm:^5.0.5" "@types/jest": "npm:^30.0.0" @@ -15424,6 +16412,7 @@ __metadata: prettier: "npm:^3.4.2" protobufjs: "npm:^7.5.4" socket.io: "npm:^4.8.1" + starknet: "npm:9.2.1" tronweb: "npm:^6.0.0" ts-jest: "npm:^29.2.5" ts-loader: "npm:^9.5.2" @@ -15750,6 +16739,32 @@ __metadata: languageName: node linkType: hard +"starknet@npm:9.2.1": + version: 9.2.1 + resolution: "starknet@npm:9.2.1" + dependencies: + "@noble/curves": "npm:~1.7.0" + "@noble/hashes": "npm:~1.6.0" + "@scure/base": "npm:~1.2.1" + "@scure/starknet": "npm:1.1.0" + "@starknet-io/get-starknet-wallet-standard": "npm:^5.0.0" + "@starknet-io/starknet-types-010": "npm:@starknet-io/types-js@0.10.0" + "@starknet-io/starknet-types-09": "npm:@starknet-io/types-js@~0.9.1" + abi-wan-kanabi: "npm:2.2.4" + lossless-json: "npm:^4.2.0" + pako: "npm:^2.0.4" + ts-mixer: "npm:^6.0.3" + checksum: 10/f90d63c0d2c4079e3c5914c0558ca384102470e30a058e2fe822df220cd6df4c7edd2c8c684e7f0b4f7b176f6e1ef2b57a54840b5db628792faa6859cfe7040e + languageName: node + linkType: hard + +"statuses@npm:>= 1.5.0 < 2": + version: 1.5.0 + resolution: "statuses@npm:1.5.0" + checksum: 10/c469b9519de16a4bb19600205cffb39ee471a5f17b82589757ca7bd40a8d92ebb6ed9f98b5a540c5d302ccbc78f15dc03cc0280dd6e00df1335568a5d5758a5c + languageName: node + linkType: hard + "statuses@npm:^2.0.1, statuses@npm:~2.0.2": version: 2.0.2 resolution: "statuses@npm:2.0.2" @@ -16020,6 +17035,13 @@ __metadata: languageName: node linkType: hard +"tagged-tag@npm:^1.0.0": + version: 1.0.0 + resolution: "tagged-tag@npm:1.0.0" + checksum: 10/e37653df3e495daa7ea7790cb161b810b00075bba2e4d6c93fb06a709e747e3ae9da11a120d0489833203926511b39e038a2affbd9d279cfb7a2f3fcccd30b5d + languageName: node + linkType: hard + "tapable@npm:^2.1.1, tapable@npm:^2.2.0, tapable@npm:^2.2.1": version: 2.3.0 resolution: "tapable@npm:2.3.0" @@ -16194,6 +17216,13 @@ __metadata: languageName: node linkType: hard +"toidentifier@npm:1.0.0": + version: 1.0.0 + resolution: "toidentifier@npm:1.0.0" + checksum: 10/199e6bfca1531d49b3506cff02353d53ec987c9ee10ee272ca6484ed97f1fc10fb77c6c009079ca16d5c5be4a10378178c3cacdb41ce9ec954c3297c74c6053e + languageName: node + linkType: hard + "toidentifier@npm:~1.0.1": version: 1.0.1 resolution: "toidentifier@npm:1.0.1" @@ -16266,6 +17295,13 @@ __metadata: languageName: node linkType: hard +"try@npm:^1.0.3": + version: 1.0.3 + resolution: "try@npm:1.0.3" + checksum: 10/a36edd39fb419ae1fef9f71b113cc5086d414f0d784dcd59394b0818b19ba683f3a8dca4b567c3adab8305e241bb84a3f760ae7d08973eae80de9a1c7d22b7df + languageName: node + linkType: hard + "ts-api-utils@npm:^2.1.0": version: 2.1.0 resolution: "ts-api-utils@npm:2.1.0" @@ -16331,6 +17367,13 @@ __metadata: languageName: node linkType: hard +"ts-mixer@npm:^6.0.3": + version: 6.0.4 + resolution: "ts-mixer@npm:6.0.4" + checksum: 10/f20571a4a4ff7b5e1a2ff659208c1ea9d4180dda932b71d289edc99e25a2948c9048e2e676b930302ac0f8e88279e0da6022823183e67de3906a3f3a8b72ea80 + languageName: node + linkType: hard + "ts-node@npm:^10.9.2": version: 10.9.2 resolution: "ts-node@npm:10.9.2" @@ -16514,7 +17557,7 @@ __metadata: languageName: node linkType: hard -"tweetnacl@npm:^1.0.3": +"tweetnacl@npm:1.0.3, tweetnacl@npm:^1.0.3": version: 1.0.3 resolution: "tweetnacl@npm:1.0.3" checksum: 10/ca122c2f86631f3c0f6d28efb44af2a301d4a557a62a3e2460286b08e97567b258c2212e4ad1cfa22bd6a57edcdc54ba76ebe946847450ab0999e6d48ccae332 @@ -16544,6 +17587,15 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:5.0.1": + version: 5.0.1 + resolution: "type-fest@npm:5.0.1" + dependencies: + tagged-tag: "npm:^1.0.0" + checksum: 10/5ec4def4ce82e6a33cf2e1a50f7ef512226fbe85314e402155aaedd70d4aa7ccea4224a72234d5351b1b4a730b36243d5b011c147e91795d2eee0dba291c6e51 + languageName: node + linkType: hard + "type-fest@npm:^0.18.0": version: 0.18.1 resolution: "type-fest@npm:0.18.1" @@ -17072,6 +18124,18 @@ __metadata: languageName: node linkType: hard +"valibot@npm:^1.2.0": + version: 1.2.0 + resolution: "valibot@npm:1.2.0" + peerDependencies: + typescript: ">=5" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10/5f9c15e6f5a2b8eae75332a3317e46e995a1763efe1b91e57bc5064e36f0feba734367c88013d53255bdf09fb9204bf3598d2ca0c3f468c8726095b1c3551926 + languageName: node + linkType: hard + "validate-npm-package-license@npm:^3.0.1, validate-npm-package-license@npm:^3.0.4": version: 3.0.4 resolution: "validate-npm-package-license@npm:3.0.4" @@ -17142,24 +18206,24 @@ __metadata: languageName: node linkType: hard -"viem@npm:^2.40.3": - version: 2.40.3 - resolution: "viem@npm:2.40.3" +"viem@npm:2.43.5": + version: 2.43.5 + resolution: "viem@npm:2.43.5" dependencies: "@noble/curves": "npm:1.9.1" "@noble/hashes": "npm:1.8.0" "@scure/bip32": "npm:1.7.0" "@scure/bip39": "npm:1.6.0" - abitype: "npm:1.1.0" + abitype: "npm:1.2.3" isows: "npm:1.0.7" - ox: "npm:0.9.6" + ox: "npm:0.11.1" ws: "npm:8.18.3" peerDependencies: typescript: ">=5.0.4" peerDependenciesMeta: typescript: optional: true - checksum: 10/42b7cc3d799923cf6aaddf7aefee59a1e390e5d84f42f334866a501505aadc5d08fd6b3356ba7fd345fe8ece5a9a3d1f5b2d0ebc7b9020e514a18ff65a73810e + checksum: 10/b57102cd885e57d43360371b5ce6807cf2d5a3f12f23b09f30a18408833b0f0470053b3d70b5905db1f28488dca88eeadd06ab1cb568e6e23315514a225c08e6 languageName: node linkType: hard @@ -17207,6 +18271,13 @@ __metadata: languageName: node linkType: hard +"web-streams-polyfill@npm:^3.0.3": + version: 3.3.3 + resolution: "web-streams-polyfill@npm:3.3.3" + checksum: 10/8e7e13501b3834094a50abe7c0b6456155a55d7571312b89570012ef47ec2a46d766934768c50aabad10a9c30dd764a407623e8bfcc74fcb58495c29130edea9 + languageName: node + linkType: hard + "webidl-conversions@npm:^3.0.0": version: 3.0.1 resolution: "webidl-conversions@npm:3.0.1" @@ -17701,3 +18772,17 @@ __metadata: checksum: 10/b2144b38807673a4254dae06fe1a212729550609e606289c305e45c585b36fab1dbba44fe6cde90db9b28be465ec63f4c2a50867aeec6672f6bc36b6c9a361a0 languageName: node linkType: hard + +"zod@npm:^3.23.8": + version: 3.25.76 + resolution: "zod@npm:3.25.76" + checksum: 10/f0c963ec40cd96858451d1690404d603d36507c1fc9682f2dae59ab38b578687d542708a7fdbf645f77926f78c9ed558f57c3d3aa226c285f798df0c4da16995 + languageName: node + linkType: hard + +"zod@npm:^4.2.1": + version: 4.3.6 + resolution: "zod@npm:4.3.6" + checksum: 10/25fc0f62e01b557b4644bf0b393bbaf47542ab30877c37837ea8caf314a8713d220c7d7fe51f68ffa72f0e1018ddfa34d96f1973d23033f5a2a5a9b6b9d9da01 + languageName: node + linkType: hard From 7f80197607dfcb2ba72a54fbdfaddd42a6adfc77 Mon Sep 17 00:00:00 2001 From: NeOMakinG <14963751+NeOMakinG@users.noreply.github.com> Date: Wed, 25 Feb 2026 18:43:50 +0100 Subject: [PATCH 6/6] fix: bunch of polish --- .eslintcache | 1 - .gitignore | 3 +- apps/notifications-service/src/main.ts | 2 + apps/swap-service/package.json | 2 + .../migration.sql | 5 + apps/swap-service/prisma/schema.prisma | 3 + apps/swap-service/src/main.ts | 4 + .../src/polling/swap-polling.service.ts | 16 + .../src/swaps/swaps.controller.ts | 24 +- apps/swap-service/src/swaps/swaps.service.ts | 274 ++++++++++-------- apps/swap-service/src/utils/pricing.ts | 11 +- .../verification/swap-verification.service.ts | 50 +++- .../src/websocket/websocket.gateway.ts | 9 +- yarn.lock | 41 +++ 14 files changed, 282 insertions(+), 163 deletions(-) delete mode 100644 .eslintcache diff --git a/.eslintcache b/.eslintcache deleted file mode 100644 index 80e246c..0000000 --- a/.eslintcache +++ /dev/null @@ -1 +0,0 @@ -[{"/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/prisma.config.ts":"1","/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/src/app.module.ts":"2","/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/src/main.ts":"3","/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/src/notifications/notifications.controller.ts":"4","/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/src/notifications/notifications.service.ts":"5","/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/src/prisma/prisma.service.ts":"6","/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/src/websocket/websocket.gateway.ts":"7","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/prisma.config.ts":"8","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/app.module.ts":"9","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapter-init.service.ts":"10","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapter-manager.service.ts":"11","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/cosmos-sdk.service.ts":"12","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/evm.service.ts":"13","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/solana.service.ts":"14","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/utxo.service.ts":"15","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/main.ts":"16","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/polling/swap-polling.service.ts":"17","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/prisma/prisma.service.ts":"18","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/swaps/swaps.controller.ts":"19","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/swaps/swaps.service.ts":"20","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/utils/affiliateFeeAsset.ts":"21","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/utils/pricing.ts":"22","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/verification/swap-verification.service.ts":"23","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/websocket/websocket.gateway.ts":"24","/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/prisma.config.ts":"25","/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/app.module.ts":"26","/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/main.ts":"27","/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/prisma/prisma.service.ts":"28","/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/referral/referral.controller.ts":"29","/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/referral/referral.service.ts":"30","/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/users/users.controller.ts":"31","/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/users/users.service.ts":"32","/Users/0xm4king/Projects/shapeshift-backend/packages/shared-types/dist/index.d.ts":"33","/Users/0xm4king/Projects/shapeshift-backend/packages/shared-types/src/index.ts":"34","/Users/0xm4king/Projects/shapeshift-backend/packages/shared-utils/dist/index.d.ts":"35","/Users/0xm4king/Projects/shapeshift-backend/packages/shared-utils/dist/service-clients.d.ts":"36","/Users/0xm4king/Projects/shapeshift-backend/packages/shared-utils/src/index.ts":"37","/Users/0xm4king/Projects/shapeshift-backend/packages/shared-utils/src/service-clients.ts":"38","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/near.service.ts":"39","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/starknet.service.ts":"40","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/sui.service.ts":"41","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/ton.service.ts":"42","/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/tron.service.ts":"43"},{"size":147,"mtime":1765450973236,"results":"44","hashOfConfig":"45"},{"size":703,"mtime":1771956054663,"results":"46","hashOfConfig":"45"},{"size":726,"mtime":1771951821026,"results":"47","hashOfConfig":"45"},{"size":2013,"mtime":1771935244697,"results":"48","hashOfConfig":"45"},{"size":7691,"mtime":1771935481678,"results":"49","hashOfConfig":"45"},{"size":354,"mtime":1771934886563,"results":"50","hashOfConfig":"45"},{"size":2208,"mtime":1771935249221,"results":"51","hashOfConfig":"45"},{"size":147,"mtime":1765450973237,"results":"52","hashOfConfig":"45"},{"size":2119,"mtime":1771956053011,"results":"53","hashOfConfig":"45"},{"size":2602,"mtime":1771949635535,"results":"54","hashOfConfig":"45"},{"size":442,"mtime":1761207744143,"results":"55","hashOfConfig":"45"},{"size":4332,"mtime":1771935524397,"results":"56","hashOfConfig":"45"},{"size":19567,"mtime":1771949868269,"results":"57","hashOfConfig":"45"},{"size":2054,"mtime":1771935506021,"results":"58","hashOfConfig":"45"},{"size":5715,"mtime":1771949904912,"results":"59","hashOfConfig":"45"},{"size":933,"mtime":1771956325630,"results":"60","hashOfConfig":"45"},{"size":1905,"mtime":1771936133654,"results":"61","hashOfConfig":"45"},{"size":354,"mtime":1771934886634,"results":"62","hashOfConfig":"45"},{"size":3909,"mtime":1772020294719,"results":"63","hashOfConfig":"45"},{"size":33826,"mtime":1772018189724,"results":"64","hashOfConfig":"45"},{"size":1655,"mtime":1771934434277,"results":"65","hashOfConfig":"45"},{"size":1864,"mtime":1771934886650,"results":"66","hashOfConfig":"45"},{"size":53605,"mtime":1771952910019,"results":"67","hashOfConfig":"45"},{"size":2343,"mtime":1771935279438,"results":"68","hashOfConfig":"45"},{"size":147,"mtime":1765450973238,"results":"69","hashOfConfig":"45"},{"size":664,"mtime":1771956053833,"results":"70","hashOfConfig":"45"},{"size":704,"mtime":1771939475570,"results":"71","hashOfConfig":"45"},{"size":354,"mtime":1771934886652,"results":"72","hashOfConfig":"45"},{"size":3106,"mtime":1771934886653,"results":"73","hashOfConfig":"45"},{"size":8591,"mtime":1771934886655,"results":"74","hashOfConfig":"45"},{"size":2626,"mtime":1771935312250,"results":"75","hashOfConfig":"45"},{"size":11895,"mtime":1771935314037,"results":"76","hashOfConfig":"45"},{"size":2910,"mtime":1771951821026,"results":"77","hashOfConfig":"45"},{"size":2939,"mtime":1771934886657,"results":"78","hashOfConfig":"45"},{"size":988,"mtime":1771937858965,"results":"79","hashOfConfig":"45"},{"size":1349,"mtime":1771937858965,"results":"80","hashOfConfig":"45"},{"size":2655,"mtime":1771935483250,"results":"81","hashOfConfig":"45"},{"size":4106,"mtime":1771935487816,"results":"82","hashOfConfig":"45"},{"size":1855,"mtime":1771951821026,"results":"83","hashOfConfig":"45"},{"size":1688,"mtime":1771951821026,"results":"84","hashOfConfig":"45"},{"size":1588,"mtime":1771949686575,"results":"85","hashOfConfig":"45"},{"size":1588,"mtime":1771949699733,"results":"86","hashOfConfig":"45"},{"size":1818,"mtime":1771949682494,"results":"87","hashOfConfig":"45"},{"filePath":"88","messages":"89","suppressedMessages":"90","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"ebiwdt",{"filePath":"91","messages":"92","suppressedMessages":"93","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"94","messages":"95","suppressedMessages":"96","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"97","messages":"98","suppressedMessages":"99","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"100","messages":"101","suppressedMessages":"102","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"103","messages":"104","suppressedMessages":"105","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"106","messages":"107","suppressedMessages":"108","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"109","messages":"110","suppressedMessages":"111","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"112","messages":"113","suppressedMessages":"114","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"115","messages":"116","suppressedMessages":"117","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"118","messages":"119","suppressedMessages":"120","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"121","messages":"122","suppressedMessages":"123","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"124","messages":"125","suppressedMessages":"126","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"127","messages":"128","suppressedMessages":"129","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"130","messages":"131","suppressedMessages":"132","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"133","messages":"134","suppressedMessages":"135","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"136","messages":"137","suppressedMessages":"138","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"139","messages":"140","suppressedMessages":"141","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"142","messages":"143","suppressedMessages":"144","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"145","messages":"146","suppressedMessages":"147","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"148","messages":"149","suppressedMessages":"150","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"151","messages":"152","suppressedMessages":"153","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"154","messages":"155","suppressedMessages":"156","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"157","messages":"158","suppressedMessages":"159","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"160","messages":"161","suppressedMessages":"162","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"163","messages":"164","suppressedMessages":"165","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"166","messages":"167","suppressedMessages":"168","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"169","messages":"170","suppressedMessages":"171","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"172","messages":"173","suppressedMessages":"174","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"175","messages":"176","suppressedMessages":"177","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"178","messages":"179","suppressedMessages":"180","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"181","messages":"182","suppressedMessages":"183","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"184","messages":"185","suppressedMessages":"186","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"187","messages":"188","suppressedMessages":"189","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"190","messages":"191","suppressedMessages":"192","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"193","messages":"194","suppressedMessages":"195","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"196","messages":"197","suppressedMessages":"198","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"199","messages":"200","suppressedMessages":"201","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"202","messages":"203","suppressedMessages":"204","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"205","messages":"206","suppressedMessages":"207","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"208","messages":"209","suppressedMessages":"210","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"211","messages":"212","suppressedMessages":"213","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"214","messages":"215","suppressedMessages":"216","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/prisma.config.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/src/app.module.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/src/main.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/src/notifications/notifications.controller.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/src/notifications/notifications.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/src/prisma/prisma.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/notifications-service/src/websocket/websocket.gateway.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/prisma.config.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/app.module.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapter-init.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapter-manager.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/cosmos-sdk.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/evm.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/solana.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/utxo.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/main.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/polling/swap-polling.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/prisma/prisma.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/swaps/swaps.controller.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/swaps/swaps.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/utils/affiliateFeeAsset.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/utils/pricing.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/verification/swap-verification.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/websocket/websocket.gateway.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/prisma.config.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/app.module.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/main.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/prisma/prisma.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/referral/referral.controller.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/referral/referral.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/users/users.controller.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/user-service/src/users/users.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/packages/shared-types/dist/index.d.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/packages/shared-types/src/index.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/packages/shared-utils/dist/index.d.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/packages/shared-utils/dist/service-clients.d.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/packages/shared-utils/src/index.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/packages/shared-utils/src/service-clients.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/near.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/starknet.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/sui.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/ton.service.ts",[],[],"/Users/0xm4king/Projects/shapeshift-backend/apps/swap-service/src/lib/chain-adapters/tron.service.ts",[],[]] \ No newline at end of file diff --git a/.gitignore b/.gitignore index 04f4e1f..12a6b26 100644 --- a/.gitignore +++ b/.gitignore @@ -64,4 +64,5 @@ report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json Pulumi.*.yaml /generated/prisma -*.db \ No newline at end of file +*.db +.eslintcache diff --git a/apps/notifications-service/src/main.ts b/apps/notifications-service/src/main.ts index d950918..a9a3a86 100644 --- a/apps/notifications-service/src/main.ts +++ b/apps/notifications-service/src/main.ts @@ -18,6 +18,8 @@ async function bootstrap() { res.status(200).json({ status: 'ok' }); }); + app.enableShutdownHooks(); + const port = process.env.NOTIFICATIONS_SERVICE_PORT || process.env.PORT || 3003; await app.listen(port); diff --git a/apps/swap-service/package.json b/apps/swap-service/package.json index 79cef4c..e814148 100644 --- a/apps/swap-service/package.json +++ b/apps/swap-service/package.json @@ -20,6 +20,8 @@ "@shapeshift/shared-types": "workspace:*", "@shapeshift/shared-utils": "workspace:*", "axios": "^1.6.2", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.3", "prisma": "6.13.0" } } diff --git a/apps/swap-service/prisma/migrations/20260223000000_add_affiliate_address/migration.sql b/apps/swap-service/prisma/migrations/20260223000000_add_affiliate_address/migration.sql index edcebd2..56c4b5c 100644 --- a/apps/swap-service/prisma/migrations/20260223000000_add_affiliate_address/migration.sql +++ b/apps/swap-service/prisma/migrations/20260223000000_add_affiliate_address/migration.sql @@ -1,5 +1,10 @@ -- AlterTable ALTER TABLE "public"."swaps" ADD COLUMN "affiliateAddress" TEXT; +ALTER TABLE "public"."swaps" ADD COLUMN "affiliateBps" TEXT; +ALTER TABLE "public"."swaps" ADD COLUMN "origin" TEXT; +ALTER TABLE "public"."swaps" ADD COLUMN "affiliateFeeAssetId" TEXT; +ALTER TABLE "public"."swaps" ADD COLUMN "affiliateFeeAmountCryptoBaseUnit" TEXT; +ALTER TABLE "public"."swaps" ADD COLUMN "pollFailCount" INTEGER NOT NULL DEFAULT 0; -- CreateIndex CREATE INDEX "swaps_affiliateAddress_idx" ON "public"."swaps"("affiliateAddress"); diff --git a/apps/swap-service/prisma/schema.prisma b/apps/swap-service/prisma/schema.prisma index 13b8793..b297978 100644 --- a/apps/swap-service/prisma/schema.prisma +++ b/apps/swap-service/prisma/schema.prisma @@ -50,8 +50,11 @@ model Swap { origin String? affiliateFeeAssetId String? affiliateFeeAmountCryptoBaseUnit String? + pollFailCount Int @default(0) @@index([referralCode]) @@index([affiliateAddress]) + @@index([status, sellTxHash]) + @@index([userId]) @@map("swaps") } diff --git a/apps/swap-service/src/main.ts b/apps/swap-service/src/main.ts index 44144db..3e9409b 100644 --- a/apps/swap-service/src/main.ts +++ b/apps/swap-service/src/main.ts @@ -1,4 +1,5 @@ import { NestFactory } from '@nestjs/core'; + import type { Response } from 'express'; import { AppModule } from './app.module'; import { ChainAdapterInitService } from './lib/chain-adapter-init.service'; @@ -23,6 +24,9 @@ async function bootstrap() { res.status(200).json({ status: 'ok' }); }); + // TODO: Add ValidationPipe when class-validator resolves properly in Yarn workspaces + app.enableShutdownHooks(); + const port = process.env.SWAP_SERVICE_PORT || process.env.PORT || 3001; await app.listen(port); diff --git a/apps/swap-service/src/polling/swap-polling.service.ts b/apps/swap-service/src/polling/swap-polling.service.ts index 1c4d7e8..ac271ba 100644 --- a/apps/swap-service/src/polling/swap-polling.service.ts +++ b/apps/swap-service/src/polling/swap-polling.service.ts @@ -6,6 +6,7 @@ import { WebsocketGateway } from '../websocket/websocket.gateway'; @Injectable() export class SwapPollingService { private readonly logger = new Logger(SwapPollingService.name); + private isPolling = false; constructor( private swapsService: SwapsService, @@ -14,6 +15,9 @@ export class SwapPollingService { @Cron(CronExpression.EVERY_5_SECONDS) async pollPendingSwaps() { + if (this.isPolling) return; + this.isPolling = true; + try { this.logger.log('Starting to poll pending swaps...'); @@ -52,10 +56,22 @@ export class SwapPollingService { } } catch (error) { this.logger.error(`Failed to poll swap ${swap.swapId}:`, error); + const failCount = await this.swapsService.incrementPollFailCount( + swap.id, + ); + if (failCount >= 100) { + await this.swapsService.updateSwapStatus({ + swapId: swap.swapId, + status: 'FAILED', + statusMessage: `Polling failed after ${failCount} attempts`, + }); + } } } } catch (error) { this.logger.error('Failed to poll pending swaps:', error); + } finally { + this.isPolling = false; } } } diff --git a/apps/swap-service/src/swaps/swaps.controller.ts b/apps/swap-service/src/swaps/swaps.controller.ts index ec1049e..3f01a39 100644 --- a/apps/swap-service/src/swaps/swaps.controller.ts +++ b/apps/swap-service/src/swaps/swaps.controller.ts @@ -7,6 +7,7 @@ import { Param, Body, Query, + NotFoundException, } from '@nestjs/common'; import { SwapsService } from './swaps.service'; import { SwapPollingService } from '../polling/swap-polling.service'; @@ -92,12 +93,10 @@ export class SwapsController { @Get(':swapId') async getSwap(@Param('swapId') swapId: string) { - const swap = await this.swapsService['prisma'].swap.findUnique({ - where: { swapId }, - }); + const swap = await this.swapsService.findSwapBySwapId(swapId); if (!swap) { - return null; + throw new NotFoundException(`Swap ${swapId} not found`); } return { @@ -109,17 +108,7 @@ export class SwapsController { @Delete('test-cleanup') async cleanupTestSwaps() { - const result = await this.swapsService['prisma'].swap.updateMany({ - where: { - swapId: { startsWith: 'test-' }, - status: { in: ['IDLE', 'PENDING'] }, - }, - data: { - status: 'FAILED', - statusMessage: 'Cleaned up by test runner', - }, - }); - return { cleaned: result.count }; + return this.swapsService.cleanupTestSwaps(); } @Post(':swapId/verify-affiliate') @@ -127,10 +116,7 @@ export class SwapsController { @Param('swapId') swapId: string, @Body() data: Omit, ) { - // Fetch the swap to get metadata and other details - const swap = await this.swapsService['prisma'].swap.findUnique({ - where: { swapId }, - }); + const swap = await this.swapsService.findSwapBySwapId(swapId); if (!swap) { return { diff --git a/apps/swap-service/src/swaps/swaps.service.ts b/apps/swap-service/src/swaps/swaps.service.ts index b17f00e..1f52e94 100644 --- a/apps/swap-service/src/swaps/swaps.service.ts +++ b/apps/swap-service/src/swaps/swaps.service.ts @@ -167,10 +167,18 @@ export class SwapsService { txLink: data.txLink, statusMessage: data.statusMessage, actualBuyAmountCryptoPrecision: data.actualBuyAmountCryptoPrecision, + pollFailCount: 0, }, }); - await this.sendStatusUpdateNotification(swap); + try { + await this.sendStatusUpdateNotification(swap); + } catch (notifError) { + this.logger.error( + `Failed to send notification for swap ${data.swapId}:`, + notifError, + ); + } this.logger.log(`Swap status updated: ${swap.swapId} -> ${data.status}`); return { @@ -365,19 +373,22 @@ export class SwapsService { } } - // Fetch all prices in parallel - const pricePromises = Array.from(uniqueAssets.values()).map( - async (asset) => { - const price = await getAssetPriceUsd(asset); - return { assetId: asset.assetId, price }; - }, - ); - - const prices = await Promise.all(pricePromises); + // Fetch prices in batches to respect CoinGecko rate limits + const BATCH_SIZE = 5; + const allReferralAssets = Array.from(uniqueAssets.values()); const priceMap = new Map(); - prices.forEach(({ assetId, price }) => { - priceMap.set(assetId, price); - }); + for (let i = 0; i < allReferralAssets.length; i += BATCH_SIZE) { + const batch = allReferralAssets.slice(i, i + BATCH_SIZE); + const batchResults = await Promise.all( + batch.map(async (asset) => { + const price = await getAssetPriceUsd(asset); + return { assetId: asset.assetId, price }; + }), + ); + batchResults.forEach(({ assetId, price }) => { + priceMap.set(assetId, price); + }); + } // Calculate period fees and volume for (const swap of periodSwaps) { @@ -583,138 +594,46 @@ export class SwapsService { } } - const pricePromises = Array.from(uniqueAssets.values()).map( - async (asset) => { - const price = await getAssetPriceUsd(asset); - return { assetId: asset.assetId, price }; - }, - ); - - const prices = await Promise.all(pricePromises); + const BATCH_SIZE = 5; + const allAffiliateAssets = Array.from(uniqueAssets.values()); const priceMap = new Map(); - prices.forEach(({ assetId, price }) => { - priceMap.set(assetId, price); - }); + for (let i = 0; i < allAffiliateAssets.length; i += BATCH_SIZE) { + const batch = allAffiliateAssets.slice(i, i + BATCH_SIZE); + const batchResults = await Promise.all( + batch.map(async (asset) => { + const price = await getAssetPriceUsd(asset); + return { assetId: asset.assetId, price }; + }), + ); + batchResults.forEach(({ assetId, price }) => { + priceMap.set(assetId, price); + }); + } let periodCommissionUsd = 0; let totalSwapVolumeUsd = 0; const swapCount = periodSwaps.length; for (const swap of periodSwaps) { - const sellAmountUsd = await this.resolveSwapUsdValue( + const { feeUsd, volumeUsd } = await this.calculateFeeForSwap( swap, priceMap, freezeCutoff, calculateUsdValue, ); - if (sellAmountUsd === null) continue; - - totalSwapVolumeUsd += sellAmountUsd; - - const verificationDetails = - swap.affiliateVerificationDetails as AffiliateVerificationDetails | null; - const verifiedBps = - verificationDetails?.affiliateBps ?? - (swap.affiliateBps ? parseInt(String(swap.affiliateBps)) : undefined); - - if (verifiedBps && sellAmountUsd > 0) { - const commissionRate = this.getAffiliateCommissionRate( - swap.origin, - verifiedBps, - ); - - const verifiedSell = - verificationDetails?.verifiedSellAmountCryptoBaseUnit; - const effectiveSellAmount = verifiedSell - ? bnOrZero(verifiedSell).lt(bnOrZero(swap.sellAmountCryptoBaseUnit)) - ? verifiedSell - : swap.sellAmountCryptoBaseUnit - : swap.sellAmountCryptoBaseUnit; - - let totalFeeUsd: number; - const feeAssetId = swap.affiliateFeeAssetId; - const feeAssetPrice = feeAssetId ? priceMap.get(feeAssetId) : null; - - if (feeAssetId && feeAssetPrice) { - const sellAssetObj = swap.sellAsset as Asset; - const buyAssetObj = swap.buyAsset as Asset; - const feeAmountBaseUnit = this.estimateAffiliateFeeAmount( - verifiedBps, - swap.swapperName, - effectiveSellAmount, - swap.expectedBuyAmountCryptoBaseUnit, - ); - const feeAssetPrecision = - feeAssetId === sellAssetObj.assetId - ? sellAssetObj.precision - : buyAssetObj.precision; - const feeAmountHuman = - parseFloat(feeAmountBaseUnit) / Math.pow(10, feeAssetPrecision); - totalFeeUsd = feeAmountHuman * feeAssetPrice; - } else { - totalFeeUsd = (sellAmountUsd * verifiedBps) / 10000; - } - - periodCommissionUsd += totalFeeUsd * commissionRate; - } + totalSwapVolumeUsd += volumeUsd; + periodCommissionUsd += feeUsd; } let allTimeCommissionUsd = 0; for (const swap of allTimeSwaps) { - const sellAmountUsd = await this.resolveSwapUsdValue( + const { feeUsd } = await this.calculateFeeForSwap( swap, priceMap, freezeCutoff, calculateUsdValue, ); - if (sellAmountUsd === null) continue; - - const verificationDetails = - swap.affiliateVerificationDetails as AffiliateVerificationDetails | null; - const verifiedBps = - verificationDetails?.affiliateBps ?? - (swap.affiliateBps ? parseInt(String(swap.affiliateBps)) : undefined); - - if (verifiedBps && sellAmountUsd > 0) { - const commissionRate = this.getAffiliateCommissionRate( - swap.origin, - verifiedBps, - ); - - const verifiedSell = - verificationDetails?.verifiedSellAmountCryptoBaseUnit; - const effectiveSellAmount = verifiedSell - ? bnOrZero(verifiedSell).lt(bnOrZero(swap.sellAmountCryptoBaseUnit)) - ? verifiedSell - : swap.sellAmountCryptoBaseUnit - : swap.sellAmountCryptoBaseUnit; - - let totalFeeUsd: number; - const feeAssetId = swap.affiliateFeeAssetId; - const feeAssetPrice = feeAssetId ? priceMap.get(feeAssetId) : null; - - if (feeAssetId && feeAssetPrice) { - const sellAssetObj = swap.sellAsset as Asset; - const buyAssetObj = swap.buyAsset as Asset; - const feeAmountBaseUnit = this.estimateAffiliateFeeAmount( - verifiedBps, - swap.swapperName, - effectiveSellAmount, - swap.expectedBuyAmountCryptoBaseUnit, - ); - const feeAssetPrecision = - feeAssetId === sellAssetObj.assetId - ? sellAssetObj.precision - : buyAssetObj.precision; - const feeAmountHuman = - parseFloat(feeAmountBaseUnit) / Math.pow(10, feeAssetPrecision); - totalFeeUsd = feeAmountHuman * feeAssetPrice; - } else { - totalFeeUsd = (sellAmountUsd * verifiedBps) / 10000; - } - - allTimeCommissionUsd += totalFeeUsd * commissionRate; - } + allTimeCommissionUsd += feeUsd; } this.logger.log( @@ -749,6 +668,85 @@ export class SwapsService { return (verifiedBps - SwapsService.API_BASE_BPS) / verifiedBps; } + private async calculateFeeForSwap( + swap: { + id: string; + swapId: string; + swapperName: string; + sellAsset: unknown; + buyAsset: unknown; + sellAmountCryptoBaseUnit: string; + sellAmountCryptoPrecision: string; + expectedBuyAmountCryptoBaseUnit: string; + sellAmountUsd: string | null; + affiliateBps: string | number | null; + origin: string | null; + affiliateFeeAssetId: string | null; + affiliateVerificationDetails: unknown; + createdAt: Date; + }, + priceMap: Map, + freezeCutoff: Date, + calculateUsdValue: (amount: string, price: number) => string, + ): Promise<{ feeUsd: number; volumeUsd: number }> { + const sellAmountUsd = await this.resolveSwapUsdValue( + swap, + priceMap, + freezeCutoff, + calculateUsdValue, + ); + if (sellAmountUsd === null) return { feeUsd: 0, volumeUsd: 0 }; + + const verificationDetails = + swap.affiliateVerificationDetails as AffiliateVerificationDetails | null; + const verifiedBps = + verificationDetails?.affiliateBps ?? + (swap.affiliateBps ? parseInt(String(swap.affiliateBps)) : undefined); + + if (!verifiedBps || sellAmountUsd <= 0) { + return { feeUsd: 0, volumeUsd: sellAmountUsd }; + } + + const commissionRate = this.getAffiliateCommissionRate( + swap.origin, + verifiedBps, + ); + + const verifiedSell = verificationDetails?.verifiedSellAmountCryptoBaseUnit; + const effectiveSellAmount = verifiedSell + ? bnOrZero(verifiedSell).lt(bnOrZero(swap.sellAmountCryptoBaseUnit)) + ? verifiedSell + : swap.sellAmountCryptoBaseUnit + : swap.sellAmountCryptoBaseUnit; + + let totalFeeUsd: number; + const feeAssetId = swap.affiliateFeeAssetId; + const feeAssetPrice = feeAssetId ? priceMap.get(feeAssetId) : null; + + if (feeAssetId && feeAssetPrice) { + const sellAssetObj = swap.sellAsset as Asset; + const buyAssetObj = swap.buyAsset as Asset; + const feeAmountBaseUnit = this.estimateAffiliateFeeAmount( + verifiedBps, + swap.swapperName, + effectiveSellAmount, + swap.expectedBuyAmountCryptoBaseUnit, + ); + const feeAssetPrecision = + feeAssetId === sellAssetObj.assetId + ? sellAssetObj.precision + : buyAssetObj.precision; + const feeAmountHuman = bnOrZero(feeAmountBaseUnit) + .div(bnOrZero(10).pow(feeAssetPrecision)) + .toNumber(); + totalFeeUsd = feeAmountHuman * feeAssetPrice; + } else { + totalFeeUsd = (sellAmountUsd * verifiedBps) / 10000; + } + + return { feeUsd: totalFeeUsd * commissionRate, volumeUsd: sellAmountUsd }; + } + private estimateAffiliateFeeAmount( affiliateBps: number, swapperName: string, @@ -1000,4 +998,32 @@ export class SwapsService { }; } } + + async incrementPollFailCount(swapId: string): Promise { + const updated = await this.prisma.swap.update({ + where: { id: swapId }, + data: { pollFailCount: { increment: 1 } }, + }); + return updated.pollFailCount; + } + + async findSwapBySwapId(swapId: string) { + return this.prisma.swap.findUnique({ + where: { swapId }, + }); + } + + async cleanupTestSwaps() { + const result = await this.prisma.swap.updateMany({ + where: { + swapId: { startsWith: 'test-' }, + status: { in: ['IDLE', 'PENDING'] }, + }, + data: { + status: 'FAILED', + statusMessage: 'Cleaned up by test runner', + }, + }); + return { cleaned: result.count }; + } } diff --git a/apps/swap-service/src/utils/pricing.ts b/apps/swap-service/src/utils/pricing.ts index 256f51a..8de92f0 100644 --- a/apps/swap-service/src/utils/pricing.ts +++ b/apps/swap-service/src/utils/pricing.ts @@ -1,7 +1,10 @@ import axios from 'axios'; +import { Logger } from '@nestjs/common'; import { Asset } from '@shapeshiftoss/types'; import { adapters } from '@shapeshiftoss/caip'; +const logger = new Logger('Pricing'); + // Simple in-memory cache const priceCache = new Map(); const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes @@ -28,7 +31,7 @@ export async function getAssetPriceUsd(asset: Asset): Promise { const url = adapters.makeCoingeckoAssetUrl(asset.assetId); if (!url) { - console.warn(`No CoinGecko URL mapping for assetId: ${asset.assetId}`); + logger.warn(`No CoinGecko URL mapping for assetId: ${asset.assetId}`); return null; } @@ -43,13 +46,13 @@ export async function getAssetPriceUsd(asset: Asset): Promise { priceCache.set(cacheKey, { price, timestamp: Date.now() }); return price; } else { - console.warn( + logger.warn( `No price data found for ${asset.assetId} (symbol: ${asset.symbol})`, ); return null; } } catch (error) { - console.error(`Failed to fetch price for ${asset.assetId}:`, error); + logger.error(`Failed to fetch price for ${asset.assetId}:`, error); return null; } } @@ -65,7 +68,7 @@ export function calculateUsdValue( const usdValue = amount * priceUsd; return usdValue.toFixed(2); } catch (error) { - console.error('Failed to calculate USD value:', error); + logger.error('Failed to calculate USD value:', error); return '0'; } } diff --git a/apps/swap-service/src/verification/swap-verification.service.ts b/apps/swap-service/src/verification/swap-verification.service.ts index d43bfde..7c80335 100644 --- a/apps/swap-service/src/verification/swap-verification.service.ts +++ b/apps/swap-service/src/verification/swap-verification.service.ts @@ -188,11 +188,23 @@ export class SwapVerificationService { case 'near intents': return await this.verifyNearIntents(swapId, metadata); - case 'relay': - return await this.verifyRelay( - swapId, - (metadata?.relayTransactionMetadata as { relayId: string }).relayId, - ); + case 'relay': { + const relayId = ( + metadata?.relayTransactionMetadata as + | { relayId?: string } + | undefined + )?.relayId; + if (!relayId) { + return { + isVerified: false, + hasAffiliate: false, + protocol: 'relay', + swapId, + error: 'Missing relay transaction metadata', + }; + } + return await this.verifyRelay(swapId, relayId); + } case 'cow swap': return await this.verifyCowSwap( @@ -1078,7 +1090,7 @@ export class SwapVerificationService { const zrxProxyUrl = process.env.ZRX_PROXY_URL || 'https://api.proxy.shapeshift.com/api/v1/zrx'; - const requestUrl = `${zrxProxyUrl}/trade-analytics/swap`; + const requestUrl = `${zrxProxyUrl}/trade-analytics/swap?txHash=${tradeHash}`; const response = await firstValueFrom( this.httpService.get(requestUrl), @@ -1227,7 +1239,7 @@ export class SwapVerificationService { })}`, ); this.logger.log( - `Bebop API Request - Headers: { 'source-auth': '${apiKey.substring(0, 8)}...' }`, + `Bebop API Request - Headers: { 'source-auth': '[REDACTED]' }`, ); this.logger.log(`Bebop API Request - Looking for txHash: ${txHash}`); @@ -1325,6 +1337,7 @@ export class SwapVerificationService { process.env.SHAPESHIFT_JUPITER_REFERRAL_KEY || 'Ajgmo453yGmcHDPoJBrMUj3GFwLVL7HaaZGNLkB8vREG'; + // TODO: Implement on-chain/API verification for Jupiter const affiliateBps = metadata?.affiliateBps ? parseInt(metadata.affiliateBps as string) : undefined; @@ -1341,7 +1354,7 @@ export class SwapVerificationService { ); return Promise.resolve({ - isVerified: true, + isVerified: false, hasAffiliate, affiliateBps: hasAffiliate ? affiliateBps : undefined, affiliateAddress: referralKey, @@ -1352,6 +1365,7 @@ export class SwapVerificationService { txHash, affiliateBps: metadata?.affiliateBps as string | undefined, referralKey, + verificationMethod: 'client_metadata_only', }, }); } catch (error) { @@ -1491,6 +1505,7 @@ export class SwapVerificationService { } try { + // TODO: Implement on-chain/API verification for Cetus const affiliateBps = metadata?.affiliateBps ? parseInt(metadata.affiliateBps as string) : undefined; @@ -1507,7 +1522,7 @@ export class SwapVerificationService { ); return Promise.resolve({ - isVerified: true, + isVerified: false, hasAffiliate, affiliateBps: hasAffiliate ? affiliateBps : undefined, verifiedSellAmountCryptoBaseUnit, @@ -1516,6 +1531,7 @@ export class SwapVerificationService { details: { txHash, affiliateBps: metadata?.affiliateBps as string | undefined, + verificationMethod: 'client_metadata_only', }, }); } catch (error) { @@ -1549,6 +1565,7 @@ export class SwapVerificationService { } try { + // TODO: Implement on-chain/API verification for Sun.io const affiliateBps = metadata?.affiliateBps ? parseInt(metadata.affiliateBps as string) : undefined; @@ -1565,7 +1582,7 @@ export class SwapVerificationService { ); return Promise.resolve({ - isVerified: true, + isVerified: false, hasAffiliate, affiliateBps: hasAffiliate ? affiliateBps : undefined, verifiedSellAmountCryptoBaseUnit, @@ -1574,6 +1591,7 @@ export class SwapVerificationService { details: { txHash, affiliateBps: metadata?.affiliateBps as string | undefined, + verificationMethod: 'client_metadata_only', }, }); } catch (error) { @@ -1607,6 +1625,7 @@ export class SwapVerificationService { } try { + // TODO: Implement on-chain/API verification for AVNU const affiliateBps = metadata?.affiliateBps ? parseInt(metadata.affiliateBps as string) : undefined; @@ -1626,7 +1645,7 @@ export class SwapVerificationService { ); return Promise.resolve({ - isVerified: true, + isVerified: false, hasAffiliate, affiliateBps: hasAffiliate ? affiliateBps : undefined, affiliateAddress, @@ -1639,6 +1658,7 @@ export class SwapVerificationService { integratorFeeRecipient: metadata?.integratorFeeRecipient as | string | undefined, + verificationMethod: 'client_metadata_only', }, }); } catch (error) { @@ -1672,6 +1692,7 @@ export class SwapVerificationService { } try { + // TODO: Implement on-chain/API verification for STON.fi const stonfiSpecific = metadata?.stonfiSpecific as | StonfiQuoteMetadata | undefined; @@ -1698,7 +1719,7 @@ export class SwapVerificationService { ); return Promise.resolve({ - isVerified: true, + isVerified: false, hasAffiliate, affiliateBps: hasAffiliate ? affiliateBps : undefined, affiliateAddress: referrerAddress, @@ -1712,6 +1733,7 @@ export class SwapVerificationService { stonfiSpecific: metadata?.stonfiSpecific as | Record | undefined, + verificationMethod: 'client_metadata_only', }, }); } catch (error) { @@ -1757,6 +1779,7 @@ export class SwapVerificationService { this.httpService.get(statusUrl), ); + // TODO: Implement on-chain/API verification for Across const depositStatus = response.data; const affiliateBps = metadata?.affiliateBps @@ -1781,7 +1804,7 @@ export class SwapVerificationService { ); return { - isVerified: true, + isVerified: false, hasAffiliate, affiliateBps: hasAffiliate ? affiliateBps : undefined, affiliateAddress, @@ -1794,6 +1817,7 @@ export class SwapVerificationService { depositStatus, integratorId: metadata?.integratorId as string | undefined, appFeeRecipient: metadata?.appFeeRecipient as string | undefined, + verificationMethod: 'client_metadata_only', }, }; } catch (error) { diff --git a/apps/swap-service/src/websocket/websocket.gateway.ts b/apps/swap-service/src/websocket/websocket.gateway.ts index a8de37b..4ef7251 100644 --- a/apps/swap-service/src/websocket/websocket.gateway.ts +++ b/apps/swap-service/src/websocket/websocket.gateway.ts @@ -18,7 +18,14 @@ interface AuthenticatedSocket extends Socket { @WebSocketGateway({ cors: { - origin: '*', + origin: process.env.ALLOWED_ORIGINS + ? process.env.ALLOWED_ORIGINS.split(',') + : [ + 'http://localhost:3000', + 'http://localhost:5173', + 'http://localhost:5174', + 'http://localhost:5175', + ], }, }) export class WebsocketGateway diff --git a/yarn.lock b/yarn.lock index 40956c7..057e1e5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5237,6 +5237,8 @@ __metadata: "@shapeshift/shared-types": "workspace:*" "@shapeshift/shared-utils": "workspace:*" axios: "npm:^1.6.2" + class-transformer: "npm:^0.5.1" + class-validator: "npm:^0.14.3" prisma: "npm:6.13.0" languageName: unknown linkType: soft @@ -7051,6 +7053,13 @@ __metadata: languageName: node linkType: hard +"@types/validator@npm:^13.15.3": + version: 13.15.10 + resolution: "@types/validator@npm:13.15.10" + checksum: 10/63117a776ced4d066d7fb63130d90ba487d38209dd45c25641ca1a6f5040e8394cc9a855750b919b72a923c5ffb51f8474f213b10b5aaa27d9db108bef07ad10 + languageName: node + linkType: hard + "@types/ws@npm:^7.4.4": version: 7.4.7 resolution: "@types/ws@npm:7.4.7" @@ -9196,6 +9205,24 @@ __metadata: languageName: node linkType: hard +"class-transformer@npm:^0.5.1": + version: 0.5.1 + resolution: "class-transformer@npm:0.5.1" + checksum: 10/750327e3e9a5cf233c5234252f4caf6b06c437bf68a24acbdcfb06c8e0bfff7aa97c30428184813e38e08111b42871f20c5cf669ea4490f8ae837c09f08b31e7 + languageName: node + linkType: hard + +"class-validator@npm:^0.14.3": + version: 0.14.3 + resolution: "class-validator@npm:0.14.3" + dependencies: + "@types/validator": "npm:^13.15.3" + libphonenumber-js: "npm:^1.11.1" + validator: "npm:^13.15.20" + checksum: 10/492a3d3bf6db896a10bb473298b9e51cf3384a2810e32c549f1f82050786835aacc8c887b6d2f4a90aa82c4359f88252ef707f75295971db58532a73c40fffdf + languageName: node + linkType: hard + "clean-stack@npm:^2.0.0": version: 2.2.0 resolution: "clean-stack@npm:2.2.0" @@ -13380,6 +13407,13 @@ __metadata: languageName: node linkType: hard +"libphonenumber-js@npm:^1.11.1": + version: 1.12.37 + resolution: "libphonenumber-js@npm:1.12.37" + checksum: 10/f1276453e12724bf5fdff85e4ce762524a3cb8ce2bc732412b68f540bca40b37ca3595d13e54afc016adc142754157c385298cf928df063035466e0c7cbdaadd + languageName: node + linkType: hard + "libsodium-wrappers@npm:^0.7.6": version: 0.7.15 resolution: "libsodium-wrappers@npm:0.7.15" @@ -18160,6 +18194,13 @@ __metadata: languageName: node linkType: hard +"validator@npm:^13.15.20": + version: 13.15.26 + resolution: "validator@npm:13.15.26" + checksum: 10/22488ae718ca724eda81b7c8bf505005d4d70cb6ff9a319f48fd897a31d40fd9a2971af4a3288667a04c56b4f95912555495519d54a5d8d63c2572bf4970081a + languageName: node + linkType: hard + "validator@npm:^13.6.0": version: 13.15.23 resolution: "validator@npm:13.15.23"