diff --git a/.gitignore b/.gitignore index c076ebc..12a6b26 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 @@ -60,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/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..a9a3a86 100644 --- a/apps/notifications-service/src/main.ts +++ b/apps/notifications-service/src/main.ts @@ -18,10 +18,13 @@ async function bootstrap() { res.status(200).json({ status: 'ok' }); }); - const port = process.env.PORT || 3000; + app.enableShutdownHooks(); + + 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/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.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.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.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/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 new file mode 100644 index 0000000..56c4b5c --- /dev/null +++ b/apps/swap-service/prisma/migrations/20260223000000_add_affiliate_address/migration.sql @@ -0,0 +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 6be91b6..b297978 100644 --- a/apps/swap-service/prisma/schema.prisma +++ b/apps/swap-service/prisma/schema.prisma @@ -45,7 +45,16 @@ model Swap { isAffiliateVerified Boolean? affiliateVerificationDetails Json? affiliateVerifiedAt DateTime? + affiliateAddress String? + affiliateBps String? + 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/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 6c2f9b6..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,19 +20,34 @@ 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, ) {} - 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.tronChainAdapterService.initializeTronChainAdapter(); + + this.suiChainAdapterService.initializeSuiChainAdapter(); + + this.nearChainAdapterService.initializeNearChainAdapter(); + + this.starknetChainAdapterService.initializeStarknetChainAdapter(); + + this.tonChainAdapterService.initializeTonChainAdapter(); this.logger.log('All chain adapters initialized successfully'); } catch (error) { 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.ts b/apps/swap-service/src/lib/chain-adapters/evm.service.ts index aa48253..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,8 +1,34 @@ 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 { + 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, optimismChainId, @@ -12,10 +38,17 @@ import { arbitrumChainId, arbitrumNovaChainId, baseChainId, + monadChainId, + hyperEvmChainId, + plasmaChainId, + katanaChainId, } 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'; +import { EvmChainId, KnownChainIds } from '@shapeshiftoss/types'; @Injectable() export class EvmChainAdapterService { @@ -23,29 +56,78 @@ 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.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) { @@ -54,7 +136,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 +158,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 +180,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 +202,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 +224,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 +246,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 +268,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 +290,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 +312,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, @@ -252,12 +334,231 @@ 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); + 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/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/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/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 b81f95d..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,14 +1,18 @@ 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 { bitcoin, zcash } from '@shapeshiftoss/chain-adapters'; +import { btcChainId, bchChainId, dogeChainId, ltcChainId, + zecChainId, } 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 +22,18 @@ 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.initializeZcashAdapter(chainAdapterManager); this.logger.log('All UTXO chain adapters initialized successfully'); } catch (error) { @@ -36,7 +42,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 +64,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 +86,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 +108,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, @@ -124,12 +130,35 @@ 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`); } - 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.ts b/apps/swap-service/src/main.ts index aafe667..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'; @@ -8,7 +9,7 @@ async function bootstrap() { // Initialize chain adapters const chainAdapterInitService = app.get(ChainAdapterInitService); - await chainAdapterInitService.initializeChainAdapters(); + chainAdapterInitService.initializeChainAdapters(); // Enable CORS app.enableCors({ @@ -23,10 +24,13 @@ async function bootstrap() { res.status(200).json({ status: 'ok' }); }); - const port = process.env.PORT || 3000; + // 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); console.log(`Swap service is running on: http://localhost:${port}`); } -bootstrap(); +void bootstrap(); diff --git a/apps/swap-service/src/polling/swap-polling.service.ts b/apps/swap-service/src/polling/swap-polling.service.ts index cde41e0..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,11 +15,14 @@ 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...'); - + const pendingSwaps = await this.swapsService.getPendingSwaps(); - + if (pendingSwaps.length === 0) { this.logger.log('No pending swaps found'); return; @@ -28,11 +32,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,14 +49,29 @@ 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); + 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/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.ts b/apps/swap-service/src/swaps/swaps.controller.ts index 529fe44..3f01a39 100644 --- a/apps/swap-service/src/swaps/swaps.controller.ts +++ b/apps/swap-service/src/swaps/swaps.controller.ts @@ -1,13 +1,24 @@ -import { Controller, Post, Get, Put, Param, Body, Query } from '@nestjs/common'; +import { + Controller, + Post, + Get, + Put, + Delete, + Param, + Body, + Query, + NotFoundException, +} 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,14 +76,27 @@ 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({ - where: { swapId }, - }); + const swap = await this.swapsService.findSwapBySwapId(swapId); if (!swap) { - return null; + throw new NotFoundException(`Swap ${swapId} not found`); } return { @@ -82,15 +106,17 @@ export class SwapsController { }; } + @Delete('test-cleanup') + async cleanupTestSwaps() { + return this.swapsService.cleanupTestSwaps(); + } + @Post(':swapId/verify-affiliate') async verifySwapAffiliate( @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 957c30e..1f52e94 100644 --- a/apps/swap-service/src/swaps/swaps.service.ts +++ b/apps/swap-service/src/swaps/swaps.service.ts @@ -5,14 +5,42 @@ 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, SwapSource, SwapStatus } 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'; -import { NotificationsServiceClient, UserServiceClient } from '@shapeshift/shared-utils'; -import { CreateSwapDto, SwapStatusResponse, UpdateSwapStatusDto } from '@shapeshift/shared-types'; +import { + resolveAffiliateFeeAssetId, + getSwapperFeeStrategy, +} from '../utils/affiliateFeeAsset'; +import { + NotificationsServiceClient, + UserServiceClient, +} from '@shapeshift/shared-utils'; +import { + CreateSwapDto, + SwapStatusResponse, + 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 { @@ -26,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(); @@ -34,43 +67,88 @@ 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 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, - expectedBuyAmountCryptoPrecision: data.expectedBuyAmountCryptoPrecision, + expectedBuyAmountCryptoPrecision: + data.expectedBuyAmountCryptoPrecision, source: data.source, swapperName: data.swapperName, - sellAccountId: hashAccountId(data.sellAccountId), - buyAccountId: data.buyAccountId ? hashAccountId(data.buyAccountId) : null, + 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, }, }); - 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); @@ -89,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 { @@ -113,7 +199,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'; @@ -122,12 +220,16 @@ 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 || 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; + } case 'FAILED': title = 'Swap Failed'; body = `Your ${sellAsset.symbol} to ${buyAsset.symbol} swap has failed`; @@ -155,7 +257,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 +275,7 @@ export class SwapsService { }, }); - return swaps.map(swap => ({ + return swaps.map((swap) => ({ ...swap, sellAsset: swap.sellAsset, buyAsset: swap.buyAsset, @@ -186,21 +288,28 @@ export class SwapsService { status: { in: ['IDLE', 'PENDING'], }, + sellTxHash: { not: null }, }, }); - 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 = { + const periodWhereClause: Prisma.SwapWhereInput = { referralCode, isAffiliateVerified: true, status: 'SUCCESS', @@ -242,14 +351,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(); @@ -260,17 +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) { @@ -278,15 +396,20 @@ 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 - const verificationDetails = swap.affiliateVerificationDetails as any; + const verificationDetails = + swap.affiliateVerificationDetails as AffiliateVerificationDetails | null; const affiliateBps = verificationDetails?.affiliateBps; if (affiliateBps && sellAmountUsd > 0) { @@ -304,8 +427,11 @@ export class SwapsService { if (!price) continue; - const sellAmountUsd = parseFloat(calculateUsdValue(swap.sellAmountCryptoPrecision, price)); - const verificationDetails = swap.affiliateVerificationDetails as any; + const sellAmountUsd = parseFloat( + calculateUsdValue(swap.sellAmountCryptoPrecision, price), + ); + const verificationDetails = + swap.affiliateVerificationDetails as AffiliateVerificationDetails | null; const affiliateBps = verificationDetails?.affiliateBps; if (affiliateBps && sellAmountUsd > 0) { @@ -320,8 +446,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,22 +461,334 @@ 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, + endDate?: Date, + ) { + 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: Prisma.SwapWhereInput = { + 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, + } as const; + + 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 import( + '../utils/pricing' + ); + + 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 BATCH_SIZE = 5; + const allAffiliateAssets = Array.from(uniqueAssets.values()); + const priceMap = new Map(); + 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 { feeUsd, volumeUsd } = await this.calculateFeeForSwap( + swap, + priceMap, + freezeCutoff, + calculateUsdValue, + ); + totalSwapVolumeUsd += volumeUsd; + periodCommissionUsd += feeUsd; + } + + let allTimeCommissionUsd = 0; + for (const swap of allTimeSwaps) { + const { feeUsd } = await this.calculateFeeForSwap( + swap, + priceMap, + freezeCutoff, + calculateUsdValue, + ); + allTimeCommissionUsd += feeUsd; + } + + 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(), + }; + } + + 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') { + // 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; + } + + 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, + 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}`); - + const swap = await this.prisma.swap.findUnique({ where: { swapId }, }); - + if (!swap) { throw new Error(`Swap not found: ${swapId}`); } 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}`); } @@ -361,19 +799,22 @@ export class SwapsService { const status = await swapper.checkTradeStatus({ txHash: swap.sellTxHash ?? '', - chainId: sellAsset.chainId as ChainId, + chainId: sellAsset.chainId, address: swap.sellAccountId, swap: { ...swap, id: swap.swapId, createdAt: swap.createdAt.getTime(), updatedAt: swap.updatedAt.getTime(), - }, + } as unknown as SwapperSwap, 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,64 +824,130 @@ 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_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, }, 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); }, + 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), }); // Verify affiliate usage let isAffiliateVerified: boolean | undefined; - let affiliateVerificationDetails: { hasAffiliate: boolean; affiliateBps?: number; affiliateAddress?: string } | undefined; + let affiliateVerificationDetails: + | { + hasAffiliate: boolean; + affiliateBps?: number; + affiliateAddress?: string; + verifiedSellAmountCryptoBaseUnit?: 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(), + sellAssetPrecision: sellAsset.precision, + affiliateBps: swap.affiliateBps, + affiliateAddress: swap.affiliateAddress, + integratorFeeRecipient: swap.affiliateAddress, + sellAmountCryptoBaseUnit: swap.sellAmountCryptoBaseUnit, }; - 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 = { hasAffiliate: verificationResult.hasAffiliate, affiliateBps: verificationResult.affiliateBps, affiliateAddress: verificationResult.affiliateAddress, + verifiedSellAmountCryptoBaseUnit: + verificationResult.verifiedSellAmountCryptoBaseUnit, }; } @@ -458,16 +965,28 @@ 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 === TxStatus.Confirmed + ? 'SUCCESS' + : status.status === TxStatus.Failed + ? 'FAILED' + : '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, }; @@ -479,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/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.ts b/apps/swap-service/src/utils/pricing.ts index deb5239..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,12 +31,14 @@ 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; } // 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,16 +46,21 @@ 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})`); + 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; } } -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'; @@ -58,7 +68,7 @@ export function calculateUsdValue(cryptoAmount: string, priceUsd: number): strin 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 7529eee..7c80335 100644 --- a/apps/swap-service/src/verification/swap-verification.service.ts +++ b/apps/swap-service/src/verification/swap-verification.service.ts @@ -2,8 +2,155 @@ 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[]; +} + +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 = ( + 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 +178,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': @@ -39,21 +188,41 @@ export class SwapVerificationService { case 'near intents': return await this.verifyNearIntents(swapId, metadata); - case 'relay': - return await this.verifyRelay(swapId, metadata.relayTransactionMetadata.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(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); @@ -65,6 +234,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, @@ -75,7 +270,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 +291,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 +309,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 +323,8 @@ export class SwapVerificationService { this.initializeOneClickService(apiKey); - const statusResponse = await OneClickService.getExecutionStatus(depositAddress); + const statusResponse = + await OneClickService.getExecutionStatus(depositAddress); if (!statusResponse) { return { @@ -140,8 +343,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 +360,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 +401,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 +437,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 +461,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 +481,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 +511,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 +522,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 +546,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 +577,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 +605,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 +624,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 +641,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 +660,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 +675,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 +709,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 +727,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 +753,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 +764,7 @@ export class SwapVerificationService { private async verifyThorchain( swapId: string, txHash?: string, + metadata?: Record, ): Promise { if (!txHash) { return { @@ -458,13 +778,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 +801,7 @@ export class SwapVerificationService { }; } - const memo = observedTx.tx.memo; + const memo: string | undefined = observedTx.tx.memo; if (!memo) { return { isVerified: false, @@ -492,13 +814,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 +838,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 +859,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 +870,7 @@ export class SwapVerificationService { private async verifyMaya( swapId: string, txHash?: string, + metadata?: Record, ): Promise { if (!txHash) { return { @@ -544,13 +884,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 +907,7 @@ export class SwapVerificationService { }; } - const memo = observedTx.tx.memo; + const memo: string | undefined = observedTx.tx.memo; if (!memo) { return { isVerified: false, @@ -578,13 +920,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 +944,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 +965,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 +977,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 +990,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 +1001,7 @@ export class SwapVerificationService { } const response = await firstValueFrom( - this.httpService.get(statusUrl, { headers }), + this.httpService.get(statusUrl, { headers }), ); const swapData = response.data; @@ -660,14 +1020,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 +1057,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 +1070,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 +1087,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 requestUrl = `${zrxProxyUrl}/trade-analytics/swap`; + const zrxProxyUrl = + process.env.ZRX_PROXY_URL || + 'https://api.proxy.shapeshift.com/api/v1/zrx'; + const requestUrl = `${zrxProxyUrl}/trade-analytics/swap?txHash=${tradeHash}`; 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 +1118,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 +1137,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 +1167,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 +1190,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 +1230,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': '[REDACTED]' }`, + ); 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 +1274,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 +1292,7 @@ export class SwapVerificationService { hasAffiliate: hasShapeshiftAffiliate, affiliateBps, affiliateAddress: shapeshiftSource, + verifiedSellAmountCryptoBaseUnit, protocol: 'bebop', swapId, details: { @@ -895,7 +1309,528 @@ 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', + }; + } + } + + 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'; + + // TODO: Implement on-chain/API verification for Jupiter + 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: false, + hasAffiliate, + affiliateBps: hasAffiliate ? affiliateBps : undefined, + affiliateAddress: referralKey, + verifiedSellAmountCryptoBaseUnit, + protocol: 'jupiter', + swapId, + details: { + txHash, + affiliateBps: metadata?.affiliateBps as string | undefined, + referralKey, + verificationMethod: 'client_metadata_only', + }, + }); + } 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 { + // TODO: Implement on-chain/API verification for Cetus + 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: false, + hasAffiliate, + affiliateBps: hasAffiliate ? affiliateBps : undefined, + verifiedSellAmountCryptoBaseUnit, + protocol: 'cetus', + swapId, + details: { + txHash, + affiliateBps: metadata?.affiliateBps as string | undefined, + verificationMethod: 'client_metadata_only', + }, + }); + } 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 { + // TODO: Implement on-chain/API verification for Sun.io + 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: false, + hasAffiliate, + affiliateBps: hasAffiliate ? affiliateBps : undefined, + verifiedSellAmountCryptoBaseUnit, + protocol: 'sun.io', + swapId, + details: { + txHash, + affiliateBps: metadata?.affiliateBps as string | undefined, + verificationMethod: 'client_metadata_only', + }, + }); + } 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 { + // TODO: Implement on-chain/API verification for AVNU + 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: false, + hasAffiliate, + affiliateBps: hasAffiliate ? affiliateBps : undefined, + affiliateAddress, + verifiedSellAmountCryptoBaseUnit, + protocol: 'avnu', + swapId, + details: { + txHash, + affiliateBps: metadata?.affiliateBps as string | undefined, + integratorFeeRecipient: metadata?.integratorFeeRecipient as + | string + | undefined, + verificationMethod: 'client_metadata_only', + }, + }); + } 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 { + // TODO: Implement on-chain/API verification for STON.fi + 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: false, + hasAffiliate, + affiliateBps: hasAffiliate ? affiliateBps : undefined, + affiliateAddress: referrerAddress, + verifiedSellAmountCryptoBaseUnit, + protocol: 'ston.fi', + swapId, + details: { + txHash, + referrerAddress, + referrerFeeUnits, + stonfiSpecific: metadata?.stonfiSpecific as + | Record + | undefined, + verificationMethod: 'client_metadata_only', + }, + }); + } 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), + ); + + // TODO: Implement on-chain/API verification for Across + 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: false, + 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, + verificationMethod: 'client_metadata_only', + }, + }; + } 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/swap-service/src/websocket/websocket.gateway.ts b/apps/swap-service/src/websocket/websocket.gateway.ts index 8368a6b..4ef7251 100644 --- a/apps/swap-service/src/websocket/websocket.gateway.ts +++ b/apps/swap-service/src/websocket/websocket.gateway.ts @@ -18,10 +18,19 @@ 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 implements OnGatewayConnection, OnGatewayDisconnect { +export class WebsocketGateway + implements OnGatewayConnection, OnGatewayDisconnect +{ @WebSocketServer() server: Server; @@ -62,25 +71,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.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/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.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.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.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.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/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/packages/shared-types/src/index.ts b/packages/shared-types/src/index.ts index baf6642..c2cf81d 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; @@ -62,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; @@ -77,6 +80,9 @@ export interface CreateSwapDto { receiveAddress?: string; isStreaming?: boolean; metadata?: Record; + affiliateAddress?: string; + affiliateBps?: string; + origin?: 'web' | 'api' | 'widget'; } export interface UpdateSwapStatusDto { @@ -123,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.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.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; } } 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..057e1e5 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" @@ -4800,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 @@ -4830,6 +5269,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 +5302,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 +5371,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 +5453,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 +5535,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 +6365,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 +6418,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 +6507,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 +6841,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 @@ -6482,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" @@ -6932,6 +7510,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 +7698,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 +7722,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 +7747,7 @@ __metadata: optional: true zod: optional: true - checksum: 10/04eb6bb8eec9422c538995cd508db851e6e59b84fb90e20207e43a6a1d1dcb4d434abacb7c5a277a0c039447dc666e676bbc1548e917e554c499ce86443416a5 + checksum: 10/94e744c2fc301b1cff59163a21b499aae0ddecdf4d3bef1579ff16b705e6f5738fd314125d791ed142487db2473d4fadcdbabb1e05e4b5d35715bc4ef35e400a languageName: node linkType: hard @@ -7262,9 +7865,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 +7875,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 +7889,7 @@ __metadata: peerDependenciesMeta: ajv: optional: true - checksum: 10/70c263ded219bf277ffd9127f793b625f10a46113b2e901e150da41931fcfd7f5592da6d66862f4449bb157ffe65867c3294a7df1d661cc232c4163d5a1718ed + checksum: 10/5679b9f9ced9d0213a202a37f3aa91efcffe59a6de1a6e3da5c873344d3c161820a1f11cc29899661fee36271fd2895dd3851b6461c902a752ad661d1c1e8722 languageName: node linkType: hard @@ -7310,6 +7913,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 +8002,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 +8149,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 +8196,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 +8216,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 +8310,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 +8434,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 +8476,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 +8508,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 +8599,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 +8637,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 +8742,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 +8769,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 +8944,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 +9044,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" @@ -8517,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" @@ -8917,6 +9623,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 +9663,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 +9831,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 +10467,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 +10749,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 +10825,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 +10897,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 +11007,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 +11141,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 +11193,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 +11219,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 +11802,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 +11838,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 +11939,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 +12018,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 +12500,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 +13207,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 +13237,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 +13334,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" @@ -12586,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" @@ -12650,6 +13478,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 +13520,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 +13562,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 +13624,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 +14136,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 +14240,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 +14290,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 +14397,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 +14420,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 +14448,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 +14711,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 +14857,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 +14867,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/9fb1a89c710c02366fe2fdda7db4e17474336456d3b80fa24e059b3d269b45e94abfe950d7f52b220abc33cde2b75d03d24f31493382a6bab37ec5aada2bd925 + 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/7e6624a91b2064c160af1bde0febb8044a35a4353885afa1cf2993f4755cad5f485857469393455a4fce3f4bc0e95f18b3dd34d5f59fd07c25fcc7ddb3047bf4 languageName: node linkType: hard @@ -14079,7 +15040,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 +15572,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 +15581,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 +15627,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 +15816,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 +15917,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 +16096,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 +16114,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 +16208,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 +16376,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 +16390,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 +16408,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 +16427,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 +16446,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 +16773,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 +17069,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 +17250,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 +17329,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 +17401,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 +17591,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 +17621,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 +18158,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" @@ -17096,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" @@ -17142,24 +18247,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 +18312,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 +18813,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