diff --git a/backend/services/mining-wallet-service/prisma/migrations/20260112220000_remove_blockchain_tables/migration.sql b/backend/services/mining-wallet-service/prisma/migrations/20260112220000_remove_blockchain_tables/migration.sql new file mode 100644 index 00000000..ae3a216b --- /dev/null +++ b/backend/services/mining-wallet-service/prisma/migrations/20260112220000_remove_blockchain_tables/migration.sql @@ -0,0 +1,27 @@ +-- Remove KAVA blockchain related tables and enums +-- These features are being removed from mining-wallet-service + +-- Drop tables in correct order (respecting foreign key constraints) +DROP TABLE IF EXISTS "burn_to_black_hole_records"; +DROP TABLE IF EXISTS "black_hole_contracts"; +DROP TABLE IF EXISTS "blockchain_address_bindings"; +DROP TABLE IF EXISTS "dex_swap_records"; +DROP TABLE IF EXISTS "deposit_records"; +DROP TABLE IF EXISTS "withdraw_requests"; + +-- Remove WithdrawStatus enum (check if used elsewhere first) +-- Note: PostgreSQL doesn't support DROP TYPE IF EXISTS in older versions +-- So we use a DO block to handle the case safely +DO $$ +BEGIN + DROP TYPE IF EXISTS "WithdrawStatus"; +EXCEPTION + WHEN OTHERS THEN NULL; +END $$; + +-- Update SystemAccountType enum to remove HOT_WALLET and COLD_WALLET +-- This requires recreating the enum, but existing data may use these values +-- For safety, we'll just leave the enum as is if there's data + +-- Remove BLOCKCHAIN from CounterpartyType enum +-- Same consideration - leave as is if data exists diff --git a/backend/services/mining-wallet-service/prisma/schema.prisma b/backend/services/mining-wallet-service/prisma/schema.prisma index f3899c5d..99a5bcd1 100644 --- a/backend/services/mining-wallet-service/prisma/schema.prisma +++ b/backend/services/mining-wallet-service/prisma/schema.prisma @@ -23,8 +23,6 @@ enum SystemAccountType { PROVINCE // 省级公司账户 CITY // 市级公司账户 FEE // 手续费账户 - HOT_WALLET // 热钱包(KAVA链上) - COLD_WALLET // 冷钱包(离线存储) } // 池账户类型 @@ -93,20 +91,9 @@ enum CounterpartyType { USER // 用户 SYSTEM_ACCOUNT // 系统账户 POOL // 池账户 - BLOCKCHAIN // 区块链地址 EXTERNAL // 外部 } -// 提现状态 -enum WithdrawStatus { - PENDING // 待处理 - PROCESSING // 处理中 - CONFIRMING // 链上确认中 - COMPLETED // 已完成 - FAILED // 失败 - CANCELLED // 已取消 -} - // Outbox 状态 enum OutboxStatus { PENDING @@ -402,193 +389,6 @@ model UserWalletTransaction { @@map("user_wallet_transactions") } -// ============================================================================= -// KAVA 区块链集成 -// ============================================================================= - -// 提现请求 -model WithdrawRequest { - id String @id @default(uuid()) - requestNo String @unique @map("request_no") - accountSequence String @map("account_sequence") - - // 提现信息 - assetType AssetType @map("asset_type") - amount Decimal @db.Decimal(30, 8) - fee Decimal @default(0) @db.Decimal(30, 8) - netAmount Decimal @map("net_amount") @db.Decimal(30, 8) - - // 目标地址 - toAddress String @map("to_address") - - // 状态 - status WithdrawStatus @default(PENDING) - - // 链上信息 - txHash String? @map("tx_hash") - blockNumber BigInt? @map("block_number") - confirmations Int @default(0) - - // 错误信息 - errorMessage String? @map("error_message") - - // 审核信息 - approvedBy String? @map("approved_by") - approvedAt DateTime? @map("approved_at") - - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") - completedAt DateTime? @map("completed_at") - - @@index([accountSequence]) - @@index([status]) - @@index([txHash]) - @@index([createdAt(sort: Desc)]) - @@map("withdraw_requests") -} - -// 充值记录(链上检测到的充值) -model DepositRecord { - id String @id @default(uuid()) - txHash String @unique @map("tx_hash") - - // 来源信息 - fromAddress String @map("from_address") - toAddress String @map("to_address") - - // 充值信息 - assetType AssetType @map("asset_type") - amount Decimal @db.Decimal(30, 8) - - // 链上信息 - blockNumber BigInt @map("block_number") - confirmations Int @default(0) - - // 匹配的用户(如果能匹配到) - matchedAccountSeq String? @map("matched_account_seq") - isProcessed Boolean @default(false) @map("is_processed") - processedAt DateTime? @map("processed_at") - - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") - - @@index([fromAddress]) - @@index([toAddress]) - @@index([matchedAccountSeq]) - @@index([isProcessed]) - @@index([createdAt(sort: Desc)]) - @@map("deposit_records") -} - -// DEX Swap 记录 -model DexSwapRecord { - id String @id @default(uuid()) - swapNo String @unique @map("swap_no") - accountSequence String @map("account_sequence") - - // Swap 信息 - fromAsset AssetType @map("from_asset") - toAsset AssetType @map("to_asset") - fromAmount Decimal @map("from_amount") @db.Decimal(30, 8) - toAmount Decimal @map("to_amount") @db.Decimal(30, 8) - exchangeRate Decimal @map("exchange_rate") @db.Decimal(30, 18) - - // 滑点/手续费 - slippage Decimal @default(0) @db.Decimal(10, 4) - fee Decimal @default(0) @db.Decimal(30, 8) - - // 状态 - status WithdrawStatus @default(PENDING) - - // 链上信息 - txHash String? @map("tx_hash") - blockNumber BigInt? @map("block_number") - - errorMessage String? @map("error_message") - - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") - completedAt DateTime? @map("completed_at") - - @@index([accountSequence]) - @@index([status]) - @@index([txHash]) - @@index([createdAt(sort: Desc)]) - @@map("dex_swap_records") -} - -// 链上地址绑定 -model BlockchainAddressBinding { - id String @id @default(uuid()) - accountSequence String @unique @map("account_sequence") - - // KAVA 地址 - kavaAddress String @unique @map("kava_address") - - // 验证信息 - isVerified Boolean @default(false) @map("is_verified") - verifiedAt DateTime? @map("verified_at") - verificationTxHash String? @map("verification_tx_hash") - - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") - - @@index([kavaAddress]) - @@map("blockchain_address_bindings") -} - -// 黑洞合约(KAVA 链上销毁地址) -model BlackHoleContract { - id String @id @default(uuid()) - contractAddress String @unique @map("contract_address") - name String - - // 累计销毁 - totalBurned Decimal @default(0) @map("total_burned") @db.Decimal(30, 8) - targetBurn Decimal @map("target_burn") @db.Decimal(30, 8) - remainingBurn Decimal @map("remaining_burn") @db.Decimal(30, 8) - - isActive Boolean @default(true) @map("is_active") - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") - - burnRecords BurnToBlackHoleRecord[] - - @@map("black_hole_contracts") -} - -// 销毁到黑洞的记录 -model BurnToBlackHoleRecord { - id String @id @default(uuid()) - blackHoleId String @map("black_hole_id") - - // 销毁信息 - amount Decimal @db.Decimal(30, 8) - - // 来源 - sourceType CounterpartyType @map("source_type") - sourceAccountSeq String? @map("source_account_seq") - sourceUserId String? @map("source_user_id") - sourcePoolType PoolAccountType? @map("source_pool_type") - - // 链上信息 - txHash String? @map("tx_hash") - blockNumber BigInt? @map("block_number") - - // 备注 - memo String? @db.Text - - createdAt DateTime @default(now()) @map("created_at") - - blackHole BlackHoleContract @relation(fields: [blackHoleId], references: [id]) - - @@index([blackHoleId]) - @@index([sourceAccountSeq]) - @@index([txHash]) - @@index([createdAt(sort: Desc)]) - @@map("burn_to_black_hole_records") -} - // ============================================================================= // 手续费配置 // ============================================================================= diff --git a/backend/services/mining-wallet-service/src/api/api.module.ts b/backend/services/mining-wallet-service/src/api/api.module.ts index 37caa5ee..1b992238 100644 --- a/backend/services/mining-wallet-service/src/api/api.module.ts +++ b/backend/services/mining-wallet-service/src/api/api.module.ts @@ -4,7 +4,6 @@ import { SystemAccountController } from './controllers/system-account.controller import { PoolAccountController } from './controllers/pool-account.controller'; import { UserWalletController } from './controllers/user-wallet.controller'; import { RegionController } from './controllers/region.controller'; -import { BlockchainController } from './controllers/blockchain.controller'; import { ApplicationModule } from '../application/application.module'; @Module({ @@ -15,7 +14,6 @@ import { ApplicationModule } from '../application/application.module'; PoolAccountController, UserWalletController, RegionController, - BlockchainController, ], }) export class ApiModule {} diff --git a/backend/services/mining-wallet-service/src/api/controllers/blockchain.controller.ts b/backend/services/mining-wallet-service/src/api/controllers/blockchain.controller.ts deleted file mode 100644 index ff9ae6d0..00000000 --- a/backend/services/mining-wallet-service/src/api/controllers/blockchain.controller.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { Controller, Get, Post, Body, Param, Query } from '@nestjs/common'; -import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiQuery } from '@nestjs/swagger'; -import { BlockchainIntegrationService } from '../../application/services/blockchain.service'; -import { CurrentUser, CurrentUserPayload } from '../../shared/decorators/current-user.decorator'; -import { AdminOnly } from '../../shared/guards/jwt-auth.guard'; -import { AssetType, WithdrawStatus } from '@prisma/client'; -import Decimal from 'decimal.js'; - -class CreateWithdrawRequestDto { - assetType: AssetType; - amount: string; - toAddress: string; -} - -class BindAddressDto { - kavaAddress: string; -} - -class CreateSwapRequestDto { - fromAsset: AssetType; - toAsset: AssetType; - fromAmount: string; - minToAmount: string; -} - -@ApiTags('Blockchain') -@Controller('blockchain') -@ApiBearerAuth() -export class BlockchainController { - constructor(private readonly blockchainService: BlockchainIntegrationService) {} - - // ==================== User Operations ==================== - - @Get('my/address') - @ApiOperation({ summary: '获取我的绑定地址' }) - async getMyAddress(@CurrentUser() user: CurrentUserPayload) { - return this.blockchainService.getUserAddressBinding(user.accountSequence); - } - - @Post('my/address') - @ApiOperation({ summary: '绑定KAVA地址' }) - async bindAddress( - @CurrentUser() user: CurrentUserPayload, - @Body() dto: BindAddressDto, - ) { - await this.blockchainService.bindUserAddress(user.accountSequence, dto.kavaAddress); - return { success: true, kavaAddress: dto.kavaAddress }; - } - - @Get('my/withdrawals') - @ApiOperation({ summary: '获取我的提现记录' }) - @ApiQuery({ name: 'status', required: false }) - @ApiQuery({ name: 'limit', required: false, type: Number }) - @ApiQuery({ name: 'offset', required: false, type: Number }) - async getMyWithdrawals( - @CurrentUser() user: CurrentUserPayload, - @Query('status') status?: WithdrawStatus, - @Query('limit') limit?: number, - @Query('offset') offset?: number, - ) { - return this.blockchainService.getUserWithdrawRequests(user.accountSequence, { - status, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - }); - } - - @Post('my/withdraw') - @ApiOperation({ summary: '创建提现请求' }) - async createWithdrawRequest( - @CurrentUser() user: CurrentUserPayload, - @Body() dto: CreateWithdrawRequestDto, - ) { - return this.blockchainService.createWithdrawRequest( - user.accountSequence, - dto.assetType, - new Decimal(dto.amount), - dto.toAddress, - ); - } - - @Post('my/swap') - @ApiOperation({ summary: '创建DEX Swap请求' }) - async createSwapRequest( - @CurrentUser() user: CurrentUserPayload, - @Body() dto: CreateSwapRequestDto, - ) { - return this.blockchainService.createSwapRequest( - user.accountSequence, - dto.fromAsset, - dto.toAsset, - new Decimal(dto.fromAmount), - new Decimal(dto.minToAmount), - ); - } - - // ==================== Admin Operations ==================== - - @Get('withdrawals/:id') - @AdminOnly() - @ApiOperation({ summary: '获取提现请求详情' }) - async getWithdrawRequest(@Param('id') id: string) { - return this.blockchainService.getWithdrawRequest(id); - } - - @Post('withdrawals/:id/approve') - @AdminOnly() - @ApiOperation({ summary: '审批提现请求' }) - async approveWithdraw( - @Param('id') id: string, - @CurrentUser() user: CurrentUserPayload, - ) { - return this.blockchainService.approveWithdrawRequest(id, user.userId); - } - - @Post('withdrawals/:id/execute') - @AdminOnly() - @ApiOperation({ summary: '执行链上提现' }) - async executeWithdraw(@Param('id') id: string) { - return this.blockchainService.executeWithdraw(id); - } - - @Post('withdrawals/:id/confirm') - @AdminOnly() - @ApiOperation({ summary: '确认提现完成' }) - async confirmWithdraw(@Param('id') id: string) { - return this.blockchainService.confirmWithdrawComplete(id); - } -} diff --git a/backend/services/mining-wallet-service/src/api/controllers/health.controller.ts b/backend/services/mining-wallet-service/src/api/controllers/health.controller.ts index e118d97d..d08f8ebe 100644 --- a/backend/services/mining-wallet-service/src/api/controllers/health.controller.ts +++ b/backend/services/mining-wallet-service/src/api/controllers/health.controller.ts @@ -3,7 +3,6 @@ import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; import { Public } from '../../shared/guards/jwt-auth.guard'; import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service'; import { RedisService } from '../../infrastructure/redis/redis.service'; -import { KavaBlockchainService } from '../../infrastructure/blockchain/kava-blockchain.service'; @ApiTags('Health') @Controller('health') @@ -11,7 +10,6 @@ export class HealthController { constructor( private readonly prisma: PrismaService, private readonly redis: RedisService, - private readonly kava: KavaBlockchainService, ) {} @Get() @@ -26,7 +24,6 @@ export class HealthController { checks: { database: 'unknown', redis: 'unknown', - blockchain: 'unknown', }, }; @@ -48,9 +45,6 @@ export class HealthController { checks.status = 'degraded'; } - // Blockchain check - checks.checks.blockchain = this.kava.isReady() ? 'healthy' : 'degraded'; - return checks; } diff --git a/backend/services/mining-wallet-service/src/application/application.module.ts b/backend/services/mining-wallet-service/src/application/application.module.ts index dd70a6c4..047db791 100644 --- a/backend/services/mining-wallet-service/src/application/application.module.ts +++ b/backend/services/mining-wallet-service/src/application/application.module.ts @@ -5,7 +5,6 @@ import { ScheduleModule } from '@nestjs/schedule'; import { SystemAccountService } from './services/system-account.service'; import { PoolAccountService } from './services/pool-account.service'; import { UserWalletService } from './services/user-wallet.service'; -import { BlockchainIntegrationService } from './services/blockchain.service'; import { ContributionWalletService } from './services/contribution-wallet.service'; // Schedulers @@ -23,7 +22,6 @@ import { UserRegisteredConsumer } from '../infrastructure/kafka/consumers/user-r SystemAccountService, PoolAccountService, UserWalletService, - BlockchainIntegrationService, ContributionWalletService, // Schedulers OutboxScheduler, @@ -36,7 +34,6 @@ import { UserRegisteredConsumer } from '../infrastructure/kafka/consumers/user-r SystemAccountService, PoolAccountService, UserWalletService, - BlockchainIntegrationService, ContributionWalletService, ], }) diff --git a/backend/services/mining-wallet-service/src/application/services/blockchain.service.ts b/backend/services/mining-wallet-service/src/application/services/blockchain.service.ts deleted file mode 100644 index c561ee45..00000000 --- a/backend/services/mining-wallet-service/src/application/services/blockchain.service.ts +++ /dev/null @@ -1,353 +0,0 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { BlockchainRepository } from '../../infrastructure/persistence/repositories/blockchain.repository'; -import { UserWalletRepository } from '../../infrastructure/persistence/repositories/user-wallet.repository'; -import { OutboxRepository } from '../../infrastructure/persistence/repositories/outbox.repository'; -import { KavaBlockchainService } from '../../infrastructure/blockchain/kava-blockchain.service'; -import { WithdrawRequest, DepositRecord, DexSwapRecord, AssetType, WithdrawStatus } from '@prisma/client'; -import Decimal from 'decimal.js'; -import { DomainException } from '../../shared/filters/domain-exception.filter'; - -@Injectable() -export class BlockchainIntegrationService { - private readonly logger = new Logger(BlockchainIntegrationService.name); - - constructor( - private readonly blockchainRepo: BlockchainRepository, - private readonly userWalletRepo: UserWalletRepository, - private readonly outboxRepo: OutboxRepository, - private readonly kavaService: KavaBlockchainService, - ) {} - - /** - * 创建提现请求 - */ - async createWithdrawRequest( - accountSequence: string, - assetType: AssetType, - amount: Decimal, - toAddress: string, - feeRate: Decimal = new Decimal('0.001'), - ): Promise { - // 验证地址格式 - if (!this.kavaService.isValidAddress(toAddress)) { - throw new DomainException('Invalid blockchain address', 'INVALID_ADDRESS'); - } - - // 检查用户余额 - const wallet = await this.userWalletRepo.findByAccountAndType(accountSequence, 'TOKEN_STORAGE'); - if (!wallet) { - throw new DomainException('User wallet not found', 'WALLET_NOT_FOUND'); - } - - const balance = new Decimal(wallet.balance.toString()); - if (balance.lessThan(amount)) { - throw new DomainException(`Insufficient balance: ${balance} < ${amount}`, 'INSUFFICIENT_BALANCE'); - } - - // 计算手续费 - const fee = amount.mul(feeRate); - if (fee.greaterThan(amount)) { - throw new DomainException('Fee exceeds amount', 'FEE_TOO_HIGH'); - } - - // 生成请求号 - const requestNo = `WD${Date.now()}${Math.random().toString(36).substring(2, 8).toUpperCase()}`; - - // 创建提现请求 - const request = await this.blockchainRepo.createWithdrawRequest({ - requestNo, - accountSequence, - assetType, - amount, - fee, - toAddress, - }); - - // 冻结用户余额 - await this.userWalletRepo.freezeBalance( - accountSequence, - 'TOKEN_STORAGE', - 'SHARE', - amount, - true, - { - referenceId: request.id, - referenceType: 'WITHDRAW_REQUEST', - memo: `提现冻结, 目标地址${toAddress}, 数量${amount.toFixed(8)}`, - }, - ); - - await this.outboxRepo.create({ - aggregateType: 'WithdrawRequest', - aggregateId: request.id, - eventType: 'WITHDRAW_REQUEST_CREATED', - payload: { - requestId: request.id, - requestNo, - accountSequence, - assetType, - amount: amount.toString(), - fee: fee.toString(), - toAddress, - }, - }); - - this.logger.log(`Withdraw request created: ${requestNo}`); - return request; - } - - /** - * 审批提现请求 - */ - async approveWithdrawRequest( - requestId: string, - approvedBy: string, - ): Promise { - const request = await this.blockchainRepo.findWithdrawById(requestId); - if (!request) { - throw new DomainException('Withdraw request not found', 'REQUEST_NOT_FOUND'); - } - - if (request.status !== 'PENDING') { - throw new DomainException(`Cannot approve request in status: ${request.status}`, 'INVALID_STATUS'); - } - - const updated = await this.blockchainRepo.updateWithdrawStatus(requestId, 'PROCESSING', { - approvedBy, - }); - - await this.outboxRepo.create({ - aggregateType: 'WithdrawRequest', - aggregateId: requestId, - eventType: 'WITHDRAW_REQUEST_APPROVED', - payload: { - requestId, - requestNo: request.requestNo, - approvedBy, - approvedAt: updated.approvedAt?.toISOString(), - }, - }); - - return updated; - } - - /** - * 执行链上提现 - */ - async executeWithdraw(requestId: string): Promise { - const request = await this.blockchainRepo.findWithdrawById(requestId); - if (!request) { - throw new DomainException('Withdraw request not found', 'REQUEST_NOT_FOUND'); - } - - if (request.status !== 'PROCESSING') { - throw new DomainException(`Cannot execute request in status: ${request.status}`, 'INVALID_STATUS'); - } - - if (!this.kavaService.isReady()) { - throw new DomainException('Blockchain service not ready', 'BLOCKCHAIN_NOT_READY'); - } - - try { - // 执行链上转账 - const netAmount = new Decimal(request.netAmount.toString()); - const result = await this.kavaService.sendNative(request.toAddress, netAmount); - - // 更新状态为确认中 - const updated = await this.blockchainRepo.updateWithdrawStatus(requestId, 'CONFIRMING', { - txHash: result.txHash, - blockNumber: BigInt(result.blockNumber), - }); - - await this.outboxRepo.create({ - aggregateType: 'WithdrawRequest', - aggregateId: requestId, - eventType: 'WITHDRAW_TX_SENT', - payload: { - requestId, - txHash: result.txHash, - blockNumber: result.blockNumber, - }, - }); - - return updated; - } catch (error) { - // 标记失败 - await this.blockchainRepo.updateWithdrawStatus(requestId, 'FAILED', { - errorMessage: error instanceof Error ? error.message : 'Unknown error', - }); - - // 解冻用户余额 - await this.userWalletRepo.freezeBalance( - request.accountSequence, - 'TOKEN_STORAGE', - 'SHARE', - new Decimal(request.amount.toString()), - false, - { - referenceId: requestId, - referenceType: 'WITHDRAW_REQUEST', - memo: `提现失败解冻, 原因: ${error instanceof Error ? error.message : 'Unknown error'}`, - }, - ); - - throw error; - } - } - - /** - * 确认提现完成 - */ - async confirmWithdrawComplete(requestId: string): Promise { - const request = await this.blockchainRepo.findWithdrawById(requestId); - if (!request) { - throw new DomainException('Withdraw request not found', 'REQUEST_NOT_FOUND'); - } - - if (request.status !== 'CONFIRMING' || !request.txHash) { - throw new DomainException(`Cannot confirm request in status: ${request.status}`, 'INVALID_STATUS'); - } - - // 检查链上确认 - const txStatus = await this.kavaService.getTransactionStatus(request.txHash); - if (!txStatus.confirmed || txStatus.status !== 'success') { - throw new DomainException('Transaction not confirmed yet', 'TX_NOT_CONFIRMED'); - } - - // 更新为完成 - const updated = await this.blockchainRepo.updateWithdrawStatus(requestId, 'COMPLETED', { - confirmations: txStatus.confirmations, - }); - - // 从冻结余额扣除(实际扣款) - await this.userWalletRepo.updateBalanceWithTransaction( - request.accountSequence, - 'TOKEN_STORAGE', - 'SHARE', - new Decimal(request.amount.toString()).negated(), - { - transactionType: 'WITHDRAW', - counterpartyType: 'BLOCKCHAIN', - counterpartyAddress: request.toAddress, - referenceId: requestId, - referenceType: 'WITHDRAW_REQUEST', - txHash: request.txHash, - memo: `提现成功, 目标地址${request.toAddress}, 数量${request.netAmount}, 手续费${request.fee}`, - }, - ); - - await this.outboxRepo.create({ - aggregateType: 'WithdrawRequest', - aggregateId: requestId, - eventType: 'WITHDRAW_COMPLETED', - payload: { - requestId, - txHash: request.txHash, - confirmations: txStatus.confirmations, - completedAt: updated.completedAt?.toISOString(), - }, - }); - - this.logger.log(`Withdraw completed: ${request.requestNo}`); - return updated; - } - - /** - * 绑定用户区块链地址 - */ - async bindUserAddress( - accountSequence: string, - kavaAddress: string, - ): Promise { - if (!this.kavaService.isValidAddress(kavaAddress)) { - throw new DomainException('Invalid KAVA address', 'INVALID_ADDRESS'); - } - - await this.blockchainRepo.bindAddress({ - accountSequence, - kavaAddress, - }); - - await this.outboxRepo.create({ - aggregateType: 'BlockchainAddress', - aggregateId: accountSequence, - eventType: 'ADDRESS_BOUND', - payload: { - accountSequence, - kavaAddress, - }, - }); - - this.logger.log(`Address bound for user ${accountSequence}: ${kavaAddress}`); - } - - /** - * 创建 DEX Swap 请求 - */ - async createSwapRequest( - accountSequence: string, - fromAsset: AssetType, - toAsset: AssetType, - fromAmount: Decimal, - minToAmount: Decimal, - ): Promise { - const swapNo = `SW${Date.now()}${Math.random().toString(36).substring(2, 8).toUpperCase()}`; - - // 这里应该调用 DEX 获取实际汇率 - // 简化处理:假设 1:1 汇率 - const exchangeRate = new Decimal(1); - const toAmount = fromAmount.mul(exchangeRate); - - if (toAmount.lessThan(minToAmount)) { - throw new DomainException('Slippage too high', 'SLIPPAGE_EXCEEDED'); - } - - const slippage = toAmount.minus(minToAmount).div(minToAmount).mul(100); - const fee = fromAmount.mul(new Decimal('0.003')); // 0.3% 手续费 - - const swap = await this.blockchainRepo.createSwapRecord({ - swapNo, - accountSequence, - fromAsset, - toAsset, - fromAmount, - toAmount, - exchangeRate, - slippage, - fee, - }); - - await this.outboxRepo.create({ - aggregateType: 'DexSwap', - aggregateId: swap.id, - eventType: 'SWAP_REQUEST_CREATED', - payload: { - swapId: swap.id, - swapNo, - accountSequence, - fromAsset, - toAsset, - fromAmount: fromAmount.toString(), - toAmount: toAmount.toString(), - exchangeRate: exchangeRate.toString(), - }, - }); - - return swap; - } - - async getWithdrawRequest(requestId: string): Promise { - return this.blockchainRepo.findWithdrawById(requestId); - } - - async getUserWithdrawRequests( - accountSequence: string, - options?: { status?: WithdrawStatus; limit?: number; offset?: number }, - ) { - return this.blockchainRepo.findWithdrawsByAccount(accountSequence, options); - } - - async getUserAddressBinding(accountSequence: string) { - return this.blockchainRepo.findAddressByAccount(accountSequence); - } -} diff --git a/backend/services/mining-wallet-service/src/infrastructure/blockchain/kava-blockchain.service.ts b/backend/services/mining-wallet-service/src/infrastructure/blockchain/kava-blockchain.service.ts deleted file mode 100644 index fff9f520..00000000 --- a/backend/services/mining-wallet-service/src/infrastructure/blockchain/kava-blockchain.service.ts +++ /dev/null @@ -1,271 +0,0 @@ -import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { ethers } from 'ethers'; -import Decimal from 'decimal.js'; - -export interface TransactionResult { - txHash: string; - blockNumber: number; - gasUsed: bigint; - status: 'success' | 'failed'; -} - -export interface TokenBalance { - balance: Decimal; - decimals: number; -} - -@Injectable() -export class KavaBlockchainService implements OnModuleInit { - private readonly logger = new Logger(KavaBlockchainService.name); - private provider: ethers.JsonRpcProvider; - private hotWallet: ethers.Wallet | null = null; - private blackHoleAddress: string; - private isConnected = false; - - constructor(private readonly configService: ConfigService) { - this.blackHoleAddress = this.configService.get( - 'KAVA_BLACK_HOLE_ADDRESS', - '0x000000000000000000000000000000000000dEaD', - ); - } - - async onModuleInit() { - await this.connect(); - } - - private async connect(): Promise { - try { - const rpcUrl = this.configService.get('KAVA_RPC_URL', 'https://evm.kava.io'); - const chainId = this.configService.get('KAVA_CHAIN_ID', 2222); - - this.provider = new ethers.JsonRpcProvider(rpcUrl, chainId); - - // Test connection - const network = await this.provider.getNetwork(); - this.logger.log(`Connected to KAVA network: ${network.chainId}`); - - // Initialize hot wallet if private key is provided - const privateKey = this.configService.get('KAVA_HOT_WALLET_PRIVATE_KEY'); - if (privateKey) { - this.hotWallet = new ethers.Wallet(privateKey, this.provider); - this.logger.log(`Hot wallet initialized: ${this.hotWallet.address}`); - } else { - this.logger.warn('No hot wallet private key provided - blockchain operations limited'); - } - - this.isConnected = true; - } catch (error) { - this.logger.error('Failed to connect to KAVA blockchain', error); - this.isConnected = false; - } - } - - isReady(): boolean { - return this.isConnected && this.hotWallet !== null; - } - - getHotWalletAddress(): string | null { - return this.hotWallet?.address || null; - } - - getBlackHoleAddress(): string { - return this.blackHoleAddress; - } - - /** - * 获取账户的原生币余额 - */ - async getBalance(address: string): Promise { - const balance = await this.provider.getBalance(address); - return new Decimal(ethers.formatEther(balance)); - } - - /** - * 获取当前区块号 - */ - async getCurrentBlockNumber(): Promise { - return this.provider.getBlockNumber(); - } - - /** - * 获取交易状态 - */ - async getTransactionStatus(txHash: string): Promise<{ - confirmed: boolean; - blockNumber: number | null; - confirmations: number; - status: 'success' | 'failed' | 'pending'; - }> { - const receipt = await this.provider.getTransactionReceipt(txHash); - - if (!receipt) { - return { - confirmed: false, - blockNumber: null, - confirmations: 0, - status: 'pending', - }; - } - - const currentBlock = await this.getCurrentBlockNumber(); - const confirmations = currentBlock - receipt.blockNumber; - - return { - confirmed: confirmations >= 1, - blockNumber: receipt.blockNumber, - confirmations, - status: receipt.status === 1 ? 'success' : 'failed', - }; - } - - /** - * 发送原生币(KAVA) - */ - async sendNative( - toAddress: string, - amount: Decimal, - ): Promise { - if (!this.hotWallet) { - throw new Error('Hot wallet not initialized'); - } - - const tx = await this.hotWallet.sendTransaction({ - to: toAddress, - value: ethers.parseEther(amount.toString()), - }); - - const receipt = await tx.wait(); - - if (!receipt) { - throw new Error('Transaction failed - no receipt'); - } - - return { - txHash: receipt.hash, - blockNumber: receipt.blockNumber, - gasUsed: receipt.gasUsed, - status: receipt.status === 1 ? 'success' : 'failed', - }; - } - - /** - * 发送 ERC20 代币 - */ - async sendToken( - tokenAddress: string, - toAddress: string, - amount: Decimal, - decimals: number = 18, - ): Promise { - if (!this.hotWallet) { - throw new Error('Hot wallet not initialized'); - } - - const erc20Abi = [ - 'function transfer(address to, uint256 amount) returns (bool)', - 'function balanceOf(address account) view returns (uint256)', - 'function decimals() view returns (uint8)', - ]; - - const tokenContract = new ethers.Contract(tokenAddress, erc20Abi, this.hotWallet); - - const amountWei = ethers.parseUnits(amount.toString(), decimals); - const tx = await tokenContract.transfer(toAddress, amountWei); - - const receipt = await tx.wait(); - - if (!receipt) { - throw new Error('Transaction failed - no receipt'); - } - - return { - txHash: receipt.hash, - blockNumber: receipt.blockNumber, - gasUsed: receipt.gasUsed, - status: receipt.status === 1 ? 'success' : 'failed', - }; - } - - /** - * 销毁到黑洞地址 - */ - async burnToBlackHole( - tokenAddress: string, - amount: Decimal, - decimals: number = 18, - ): Promise { - return this.sendToken(tokenAddress, this.blackHoleAddress, amount, decimals); - } - - /** - * 获取 ERC20 代币余额 - */ - async getTokenBalance( - tokenAddress: string, - walletAddress: string, - ): Promise { - const erc20Abi = [ - 'function balanceOf(address account) view returns (uint256)', - 'function decimals() view returns (uint8)', - ]; - - const tokenContract = new ethers.Contract(tokenAddress, erc20Abi, this.provider); - - const [balance, decimals] = await Promise.all([ - tokenContract.balanceOf(walletAddress), - tokenContract.decimals(), - ]); - - return { - balance: new Decimal(ethers.formatUnits(balance, decimals)), - decimals, - }; - } - - /** - * 估算 Gas 费用 - */ - async estimateGas( - toAddress: string, - value: Decimal, - data?: string, - ): Promise<{ gasLimit: bigint; gasPrice: bigint; estimatedFee: Decimal }> { - const gasPrice = (await this.provider.getFeeData()).gasPrice || 0n; - - const gasLimit = await this.provider.estimateGas({ - to: toAddress, - value: ethers.parseEther(value.toString()), - data: data || '0x', - }); - - const estimatedFee = new Decimal(ethers.formatEther(gasLimit * gasPrice)); - - return { - gasLimit, - gasPrice, - estimatedFee, - }; - } - - /** - * 验证地址格式 - */ - isValidAddress(address: string): boolean { - return ethers.isAddress(address); - } - - /** - * 监听新区块(用于检测充值) - */ - onNewBlock(callback: (blockNumber: number) => void): void { - this.provider.on('block', callback); - } - - /** - * 停止监听 - */ - removeAllListeners(): void { - this.provider.removeAllListeners(); - } -} diff --git a/backend/services/mining-wallet-service/src/infrastructure/infrastructure.module.ts b/backend/services/mining-wallet-service/src/infrastructure/infrastructure.module.ts index b2a97d67..85676954 100644 --- a/backend/services/mining-wallet-service/src/infrastructure/infrastructure.module.ts +++ b/backend/services/mining-wallet-service/src/infrastructure/infrastructure.module.ts @@ -6,12 +6,10 @@ import { SystemAccountRepository } from './persistence/repositories/system-accou import { PoolAccountRepository } from './persistence/repositories/pool-account.repository'; import { UserWalletRepository } from './persistence/repositories/user-wallet.repository'; import { RegionRepository } from './persistence/repositories/region.repository'; -import { BlockchainRepository } from './persistence/repositories/blockchain.repository'; import { OutboxRepository } from './persistence/repositories/outbox.repository'; import { ProcessedEventRepository } from './persistence/repositories/processed-event.repository'; import { RedisService } from './redis/redis.service'; import { KafkaProducerService } from './kafka/kafka-producer.service'; -import { KavaBlockchainService } from './blockchain/kava-blockchain.service'; // 注意: Consumers 移到 ApplicationModule 中,因为它们依赖应用服务 @Global() @@ -49,12 +47,10 @@ import { KavaBlockchainService } from './blockchain/kava-blockchain.service'; PoolAccountRepository, UserWalletRepository, RegionRepository, - BlockchainRepository, OutboxRepository, ProcessedEventRepository, // Services KafkaProducerService, - KavaBlockchainService, // Consumers 已移到 ApplicationModule { provide: 'REDIS_OPTIONS', @@ -74,12 +70,10 @@ import { KavaBlockchainService } from './blockchain/kava-blockchain.service'; PoolAccountRepository, UserWalletRepository, RegionRepository, - BlockchainRepository, OutboxRepository, ProcessedEventRepository, // Services KafkaProducerService, - KavaBlockchainService, RedisService, ClientsModule, ], diff --git a/backend/services/mining-wallet-service/src/infrastructure/persistence/repositories/blockchain.repository.ts b/backend/services/mining-wallet-service/src/infrastructure/persistence/repositories/blockchain.repository.ts deleted file mode 100644 index 4a434fa2..00000000 --- a/backend/services/mining-wallet-service/src/infrastructure/persistence/repositories/blockchain.repository.ts +++ /dev/null @@ -1,399 +0,0 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { PrismaService } from '../prisma/prisma.service'; -import { - WithdrawRequest, - DepositRecord, - DexSwapRecord, - BlockchainAddressBinding, - BlackHoleContract, - BurnToBlackHoleRecord, - WithdrawStatus, - AssetType, - CounterpartyType, - PoolAccountType, - Prisma, -} from '@prisma/client'; -import Decimal from 'decimal.js'; - -@Injectable() -export class BlockchainRepository { - private readonly logger = new Logger(BlockchainRepository.name); - - constructor(private readonly prisma: PrismaService) {} - - // ==================== Withdraw Requests ==================== - - async createWithdrawRequest(data: { - requestNo: string; - accountSequence: string; - assetType: AssetType; - amount: Decimal; - fee: Decimal; - toAddress: string; - }): Promise { - const netAmount = data.amount.minus(data.fee); - - return this.prisma.withdrawRequest.create({ - data: { - requestNo: data.requestNo, - accountSequence: data.accountSequence, - assetType: data.assetType, - amount: data.amount.toFixed(8), - fee: data.fee.toFixed(8), - netAmount: netAmount.toFixed(8), - toAddress: data.toAddress, - status: 'PENDING', - }, - }); - } - - async findWithdrawById(id: string): Promise { - return this.prisma.withdrawRequest.findUnique({ - where: { id }, - }); - } - - async findWithdrawByRequestNo(requestNo: string): Promise { - return this.prisma.withdrawRequest.findUnique({ - where: { requestNo }, - }); - } - - async findWithdrawsByAccount( - accountSequence: string, - options?: { - status?: WithdrawStatus; - limit?: number; - offset?: number; - }, - ): Promise<{ requests: WithdrawRequest[]; total: number }> { - const where: Prisma.WithdrawRequestWhereInput = { - accountSequence, - }; - - if (options?.status) { - where.status = options.status; - } - - const [requests, total] = await Promise.all([ - this.prisma.withdrawRequest.findMany({ - where, - orderBy: { createdAt: 'desc' }, - take: options?.limit || 50, - skip: options?.offset || 0, - }), - this.prisma.withdrawRequest.count({ where }), - ]); - - return { requests, total }; - } - - async updateWithdrawStatus( - id: string, - status: WithdrawStatus, - data?: { - txHash?: string; - blockNumber?: bigint; - confirmations?: number; - errorMessage?: string; - approvedBy?: string; - }, - ): Promise { - const updateData: Prisma.WithdrawRequestUpdateInput = { - status, - }; - - if (data?.txHash) updateData.txHash = data.txHash; - if (data?.blockNumber) updateData.blockNumber = data.blockNumber; - if (data?.confirmations) updateData.confirmations = data.confirmations; - if (data?.errorMessage) updateData.errorMessage = data.errorMessage; - if (data?.approvedBy) { - updateData.approvedBy = data.approvedBy; - updateData.approvedAt = new Date(); - } - if (status === 'COMPLETED') { - updateData.completedAt = new Date(); - } - - return this.prisma.withdrawRequest.update({ - where: { id }, - data: updateData, - }); - } - - // ==================== Deposit Records ==================== - - async createDepositRecord(data: { - txHash: string; - fromAddress: string; - toAddress: string; - assetType: AssetType; - amount: Decimal; - blockNumber: bigint; - confirmations?: number; - matchedAccountSeq?: string; - }): Promise { - return this.prisma.depositRecord.create({ - data: { - txHash: data.txHash, - fromAddress: data.fromAddress, - toAddress: data.toAddress, - assetType: data.assetType, - amount: data.amount.toFixed(8), - blockNumber: data.blockNumber, - confirmations: data.confirmations || 0, - matchedAccountSeq: data.matchedAccountSeq, - }, - }); - } - - async findDepositByTxHash(txHash: string): Promise { - return this.prisma.depositRecord.findUnique({ - where: { txHash }, - }); - } - - async findUnprocessedDeposits(limit: number = 100): Promise { - return this.prisma.depositRecord.findMany({ - where: { - isProcessed: false, - matchedAccountSeq: { not: null }, - }, - orderBy: { createdAt: 'asc' }, - take: limit, - }); - } - - async markDepositProcessed(id: string): Promise { - return this.prisma.depositRecord.update({ - where: { id }, - data: { - isProcessed: true, - processedAt: new Date(), - }, - }); - } - - async updateDepositConfirmations(txHash: string, confirmations: number): Promise { - return this.prisma.depositRecord.update({ - where: { txHash }, - data: { confirmations }, - }); - } - - // ==================== DEX Swap Records ==================== - - async createSwapRecord(data: { - swapNo: string; - accountSequence: string; - fromAsset: AssetType; - toAsset: AssetType; - fromAmount: Decimal; - toAmount: Decimal; - exchangeRate: Decimal; - slippage?: Decimal; - fee?: Decimal; - }): Promise { - return this.prisma.dexSwapRecord.create({ - data: { - swapNo: data.swapNo, - accountSequence: data.accountSequence, - fromAsset: data.fromAsset, - toAsset: data.toAsset, - fromAmount: data.fromAmount.toFixed(8), - toAmount: data.toAmount.toFixed(8), - exchangeRate: data.exchangeRate.toFixed(18), - slippage: data.slippage?.toFixed(4) || '0', - fee: data.fee?.toFixed(8) || '0', - status: 'PENDING', - }, - }); - } - - async findSwapById(id: string): Promise { - return this.prisma.dexSwapRecord.findUnique({ - where: { id }, - }); - } - - async findSwapBySwapNo(swapNo: string): Promise { - return this.prisma.dexSwapRecord.findUnique({ - where: { swapNo }, - }); - } - - async updateSwapStatus( - id: string, - status: WithdrawStatus, - data?: { - txHash?: string; - blockNumber?: bigint; - errorMessage?: string; - }, - ): Promise { - const updateData: Prisma.DexSwapRecordUpdateInput = { - status, - }; - - if (data?.txHash) updateData.txHash = data.txHash; - if (data?.blockNumber) updateData.blockNumber = data.blockNumber; - if (data?.errorMessage) updateData.errorMessage = data.errorMessage; - if (status === 'COMPLETED') { - updateData.completedAt = new Date(); - } - - return this.prisma.dexSwapRecord.update({ - where: { id }, - data: updateData, - }); - } - - // ==================== Address Binding ==================== - - async bindAddress(data: { - accountSequence: string; - kavaAddress: string; - }): Promise { - return this.prisma.blockchainAddressBinding.upsert({ - where: { accountSequence: data.accountSequence }, - create: { - accountSequence: data.accountSequence, - kavaAddress: data.kavaAddress, - }, - update: { - kavaAddress: data.kavaAddress, - isVerified: false, - verifiedAt: null, - verificationTxHash: null, - }, - }); - } - - async findAddressByAccount(accountSequence: string): Promise { - return this.prisma.blockchainAddressBinding.findUnique({ - where: { accountSequence }, - }); - } - - async findAccountByAddress(kavaAddress: string): Promise { - return this.prisma.blockchainAddressBinding.findUnique({ - where: { kavaAddress }, - }); - } - - async verifyAddress( - accountSequence: string, - verificationTxHash: string, - ): Promise { - return this.prisma.blockchainAddressBinding.update({ - where: { accountSequence }, - data: { - isVerified: true, - verifiedAt: new Date(), - verificationTxHash, - }, - }); - } - - // ==================== Black Hole Contract ==================== - - async createBlackHoleContract(data: { - contractAddress: string; - name: string; - targetBurn: Decimal; - }): Promise { - return this.prisma.blackHoleContract.create({ - data: { - contractAddress: data.contractAddress, - name: data.name, - targetBurn: data.targetBurn.toFixed(8), - remainingBurn: data.targetBurn.toFixed(8), - }, - }); - } - - async findBlackHoleContract(contractAddress: string): Promise { - return this.prisma.blackHoleContract.findUnique({ - where: { contractAddress }, - }); - } - - async getActiveBlackHoleContract(): Promise { - return this.prisma.blackHoleContract.findFirst({ - where: { isActive: true }, - }); - } - - async recordBurnToBlackHole(data: { - blackHoleId: string; - amount: Decimal; - sourceType: CounterpartyType; - sourceAccountSeq?: string; - sourceUserId?: string; - sourcePoolType?: PoolAccountType; - txHash?: string; - blockNumber?: bigint; - memo?: string; - }): Promise { - return this.prisma.$transaction(async (tx) => { - // 更新黑洞合约统计 - const blackHole = await tx.blackHoleContract.findUnique({ - where: { id: data.blackHoleId }, - }); - - if (!blackHole) { - throw new Error(`Black hole contract not found: ${data.blackHoleId}`); - } - - const newTotalBurned = new Decimal(blackHole.totalBurned.toString()).plus(data.amount); - const newRemainingBurn = new Decimal(blackHole.remainingBurn.toString()).minus(data.amount); - - await tx.blackHoleContract.update({ - where: { id: data.blackHoleId }, - data: { - totalBurned: newTotalBurned.toFixed(8), - remainingBurn: newRemainingBurn.greaterThan(0) ? newRemainingBurn.toFixed(8) : '0', - }, - }); - - // 创建销毁记录 - return tx.burnToBlackHoleRecord.create({ - data: { - blackHoleId: data.blackHoleId, - amount: data.amount.toFixed(8), - sourceType: data.sourceType, - sourceAccountSeq: data.sourceAccountSeq, - sourceUserId: data.sourceUserId, - sourcePoolType: data.sourcePoolType, - txHash: data.txHash, - blockNumber: data.blockNumber, - memo: data.memo, - }, - }); - }); - } - - async getBurnRecords( - blackHoleId: string, - options?: { - limit?: number; - offset?: number; - }, - ): Promise<{ records: BurnToBlackHoleRecord[]; total: number }> { - const where: Prisma.BurnToBlackHoleRecordWhereInput = { - blackHoleId, - }; - - const [records, total] = await Promise.all([ - this.prisma.burnToBlackHoleRecord.findMany({ - where, - orderBy: { createdAt: 'desc' }, - take: options?.limit || 50, - skip: options?.offset || 0, - }), - this.prisma.burnToBlackHoleRecord.count({ where }), - ]); - - return { records, total }; - } -}