refactor(identity-service): remove deposit/blockchain code, belongs to wallet-service
- Remove DepositController, DepositService, BlockchainQueryService - Deposit address and balance queries should be in wallet-service - identity-service now only handles user identity 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
9ae26d0f1f
commit
cf308efecf
|
|
@ -2,11 +2,10 @@ import { Module } from '@nestjs/common';
|
|||
import { UserAccountController } from './controllers/user-account.controller';
|
||||
import { AuthController } from './controllers/auth.controller';
|
||||
import { ReferralsController } from './controllers/referrals.controller';
|
||||
import { DepositController } from './controllers/deposit.controller';
|
||||
import { ApplicationModule } from '@/application/application.module';
|
||||
|
||||
@Module({
|
||||
imports: [ApplicationModule],
|
||||
controllers: [UserAccountController, AuthController, ReferralsController, DepositController],
|
||||
controllers: [UserAccountController, AuthController, ReferralsController],
|
||||
})
|
||||
export class ApiModule {}
|
||||
|
|
|
|||
|
|
@ -1,90 +0,0 @@
|
|||
import { Controller, Get, UseGuards, Request, Logger } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { JwtAuthGuard } from '@/shared/guards/jwt-auth.guard';
|
||||
import { DepositService } from '@/application/services/deposit.service';
|
||||
|
||||
/**
|
||||
* 充值地址响应 DTO
|
||||
*/
|
||||
class DepositAddressResponseDto {
|
||||
kavaAddress: string | null;
|
||||
bscAddress: string | null;
|
||||
isValid: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* USDT 余额响应 DTO
|
||||
*/
|
||||
class UsdtBalanceDto {
|
||||
chainType: string;
|
||||
address: string;
|
||||
balance: string;
|
||||
rawBalance: string;
|
||||
decimals: number;
|
||||
}
|
||||
|
||||
class BalanceResponseDto {
|
||||
kava: UsdtBalanceDto | null;
|
||||
bsc: UsdtBalanceDto | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 充值控制器
|
||||
*
|
||||
* 提供充值地址获取和余额查询 API
|
||||
*/
|
||||
@ApiTags('Deposit')
|
||||
@Controller('api/deposit')
|
||||
export class DepositController {
|
||||
private readonly logger = new Logger(DepositController.name);
|
||||
|
||||
constructor(private readonly depositService: DepositService) {}
|
||||
|
||||
/**
|
||||
* 获取充值地址
|
||||
*
|
||||
* 返回用户的 KAVA 和 BSC 充值地址
|
||||
* 会验证地址签名,确保地址未被篡改
|
||||
*/
|
||||
@Get('addresses')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: '获取充值地址' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: '成功获取充值地址',
|
||||
type: DepositAddressResponseDto,
|
||||
})
|
||||
@ApiResponse({ status: 400, description: '获取失败' })
|
||||
@ApiResponse({ status: 401, description: '未授权' })
|
||||
async getDepositAddresses(@Request() req: any): Promise<DepositAddressResponseDto> {
|
||||
const userId = req.user.userId;
|
||||
this.logger.log(`获取充值地址: userId=${userId}`);
|
||||
|
||||
return this.depositService.getDepositAddresses(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 USDT 余额
|
||||
*
|
||||
* 实时查询 KAVA 和 BSC 链上的 USDT 余额
|
||||
*/
|
||||
@Get('balances')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: '查询 USDT 余额' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: '成功获取余额',
|
||||
type: BalanceResponseDto,
|
||||
})
|
||||
@ApiResponse({ status: 400, description: '查询失败' })
|
||||
@ApiResponse({ status: 401, description: '未授权' })
|
||||
async getUsdtBalances(@Request() req: any): Promise<BalanceResponseDto> {
|
||||
const userId = req.user.userId;
|
||||
this.logger.log(`查询 USDT 余额: userId=${userId}`);
|
||||
|
||||
return this.depositService.getUsdtBalances(userId);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { UserApplicationService } from './services/user-application.service';
|
||||
import { TokenService } from './services/token.service';
|
||||
import { DepositService } from './services/deposit.service';
|
||||
import { AutoCreateAccountHandler } from './commands/auto-create-account/auto-create-account.handler';
|
||||
import { RecoverByMnemonicHandler } from './commands/recover-by-mnemonic/recover-by-mnemonic.handler';
|
||||
import { RecoverByPhoneHandler } from './commands/recover-by-phone/recover-by-phone.handler';
|
||||
|
|
@ -18,7 +17,6 @@ import { InfrastructureModule } from '@/infrastructure/infrastructure.module';
|
|||
providers: [
|
||||
UserApplicationService,
|
||||
TokenService,
|
||||
DepositService,
|
||||
AutoCreateAccountHandler,
|
||||
RecoverByMnemonicHandler,
|
||||
RecoverByPhoneHandler,
|
||||
|
|
@ -33,7 +31,6 @@ import { InfrastructureModule } from '@/infrastructure/infrastructure.module';
|
|||
exports: [
|
||||
UserApplicationService,
|
||||
TokenService,
|
||||
DepositService,
|
||||
AutoCreateAccountHandler,
|
||||
RecoverByMnemonicHandler,
|
||||
RecoverByPhoneHandler,
|
||||
|
|
|
|||
|
|
@ -1,194 +0,0 @@
|
|||
import { Injectable, Logger, BadRequestException } from '@nestjs/common';
|
||||
import { UserAccountRepositoryImpl } from '@/infrastructure/persistence/repositories/user-account.repository.impl';
|
||||
import { BlockchainQueryService, UsdtBalance } from '@/infrastructure/external/blockchain/blockchain-query.service';
|
||||
import { UserId, ChainType } from '@/domain/value-objects';
|
||||
import { WalletAddress } from '@/domain/entities/wallet-address.entity';
|
||||
|
||||
/**
|
||||
* 充值地址响应
|
||||
*/
|
||||
export interface DepositAddressResponse {
|
||||
kavaAddress: string | null;
|
||||
bscAddress: string | null;
|
||||
isValid: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 余额响应
|
||||
*/
|
||||
export interface BalanceResponse {
|
||||
kava: UsdtBalance | null;
|
||||
bsc: UsdtBalance | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 充值服务
|
||||
*
|
||||
* 提供充值地址获取和余额查询功能
|
||||
*/
|
||||
@Injectable()
|
||||
export class DepositService {
|
||||
private readonly logger = new Logger(DepositService.name);
|
||||
|
||||
constructor(
|
||||
private readonly userAccountRepository: UserAccountRepositoryImpl,
|
||||
private readonly blockchainQueryService: BlockchainQueryService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 获取用户的充值地址
|
||||
*
|
||||
* 验证地址签名是否有效,防止使用被篡改的地址
|
||||
*/
|
||||
async getDepositAddresses(userId: string): Promise<DepositAddressResponse> {
|
||||
this.logger.log(`获取充值地址: userId=${userId}`);
|
||||
|
||||
const userAccount = await this.userAccountRepository.findById(UserId.create(userId));
|
||||
if (!userAccount) {
|
||||
throw new BadRequestException('用户不存在');
|
||||
}
|
||||
|
||||
const walletAddresses = userAccount.getAllWalletAddresses();
|
||||
if (!walletAddresses || walletAddresses.length === 0) {
|
||||
return {
|
||||
kavaAddress: null,
|
||||
bscAddress: null,
|
||||
isValid: false,
|
||||
message: '充值账户异常:未找到钱包地址,请联系客服',
|
||||
};
|
||||
}
|
||||
|
||||
// 查找 KAVA 和 BSC 地址
|
||||
const kavaWallet = walletAddresses.find((w: WalletAddress) => w.chainType === ChainType.KAVA);
|
||||
const bscWallet = walletAddresses.find((w: WalletAddress) => w.chainType === ChainType.BSC);
|
||||
|
||||
// 验证地址签名
|
||||
const validationResults = await Promise.all([
|
||||
kavaWallet ? this.validateWalletAddress(kavaWallet) : Promise.resolve(true),
|
||||
bscWallet ? this.validateWalletAddress(bscWallet) : Promise.resolve(true),
|
||||
]);
|
||||
|
||||
const [kavaValid, bscValid] = validationResults;
|
||||
|
||||
// 如果有任何一个地址验证失败,返回错误
|
||||
if (!kavaValid || !bscValid) {
|
||||
this.logger.warn(`地址验证失败: userId=${userId}, kavaValid=${kavaValid}, bscValid=${bscValid}`);
|
||||
return {
|
||||
kavaAddress: null,
|
||||
bscAddress: null,
|
||||
isValid: false,
|
||||
message: '充值账户异常:地址验证失败,请重试或联系客服',
|
||||
};
|
||||
}
|
||||
|
||||
// 检查地址状态
|
||||
if (kavaWallet && kavaWallet.status !== 'ACTIVE') {
|
||||
this.logger.warn(`KAVA 地址状态异常: userId=${userId}, status=${kavaWallet.status}`);
|
||||
return {
|
||||
kavaAddress: null,
|
||||
bscAddress: null,
|
||||
isValid: false,
|
||||
message: '充值账户异常:KAVA 地址已禁用,请联系客服',
|
||||
};
|
||||
}
|
||||
|
||||
if (bscWallet && bscWallet.status !== 'ACTIVE') {
|
||||
this.logger.warn(`BSC 地址状态异常: userId=${userId}, status=${bscWallet.status}`);
|
||||
return {
|
||||
kavaAddress: null,
|
||||
bscAddress: null,
|
||||
isValid: false,
|
||||
message: '充值账户异常:BSC 地址已禁用,请联系客服',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
kavaAddress: kavaWallet?.address || null,
|
||||
bscAddress: bscWallet?.address || null,
|
||||
isValid: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询用户的 USDT 余额
|
||||
*/
|
||||
async getUsdtBalances(userId: string): Promise<BalanceResponse> {
|
||||
this.logger.log(`查询 USDT 余额: userId=${userId}`);
|
||||
|
||||
// 先获取充值地址
|
||||
const depositAddresses = await this.getDepositAddresses(userId);
|
||||
|
||||
if (!depositAddresses.isValid) {
|
||||
throw new BadRequestException(depositAddresses.message || '获取充值地址失败');
|
||||
}
|
||||
|
||||
const results: BalanceResponse = {
|
||||
kava: null,
|
||||
bsc: null,
|
||||
};
|
||||
|
||||
// 查询 KAVA 余额
|
||||
if (depositAddresses.kavaAddress) {
|
||||
try {
|
||||
results.kava = await this.blockchainQueryService.getUsdtBalance(
|
||||
'KAVA',
|
||||
depositAddresses.kavaAddress,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(`查询 KAVA USDT 余额失败: ${error.message}`);
|
||||
results.kava = {
|
||||
chainType: 'KAVA',
|
||||
address: depositAddresses.kavaAddress,
|
||||
balance: '0',
|
||||
rawBalance: '0',
|
||||
decimals: 6,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 查询 BSC 余额
|
||||
if (depositAddresses.bscAddress) {
|
||||
try {
|
||||
results.bsc = await this.blockchainQueryService.getUsdtBalance(
|
||||
'BSC',
|
||||
depositAddresses.bscAddress,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(`查询 BSC USDT 余额失败: ${error.message}`);
|
||||
results.bsc = {
|
||||
chainType: 'BSC',
|
||||
address: depositAddresses.bscAddress,
|
||||
balance: '0',
|
||||
rawBalance: '0',
|
||||
decimals: 18,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证钱包地址签名
|
||||
*/
|
||||
private async validateWalletAddress(wallet: any): Promise<boolean> {
|
||||
try {
|
||||
// 如果没有签名数据 (旧版本创建的地址),暂时允许通过
|
||||
if (!wallet.publicKey || !wallet.mpcSignature?.r) {
|
||||
this.logger.warn(`钱包地址无签名数据: chainType=${wallet.chainType}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 验证签名
|
||||
const isValid = await wallet.verifySignature();
|
||||
if (!isValid) {
|
||||
this.logger.error(`钱包地址签名验证失败: chainType=${wallet.chainType}, address=${wallet.address}`);
|
||||
}
|
||||
return isValid;
|
||||
} catch (error) {
|
||||
this.logger.error(`验证钱包地址签名异常: ${error.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { HttpService } from '@nestjs/axios';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
/**
|
||||
* USDT 余额查询结果
|
||||
*/
|
||||
export interface UsdtBalance {
|
||||
chainType: string;
|
||||
address: string;
|
||||
balance: string; // 格式化后的余额 (带小数)
|
||||
rawBalance: string; // 原始余额 (wei)
|
||||
decimals: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* blockchain-service 余额响应
|
||||
*/
|
||||
interface BlockchainBalanceResponse {
|
||||
chainType: string;
|
||||
address: string;
|
||||
usdtBalance: string;
|
||||
nativeBalance: string;
|
||||
nativeSymbol: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* blockchain-service 多链余额响应
|
||||
*/
|
||||
interface BlockchainMultiChainResponse {
|
||||
address: string;
|
||||
balances: BlockchainBalanceResponse[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 区块链查询服务
|
||||
*
|
||||
* 通过 HTTP 调用 blockchain-service 查询余额
|
||||
*/
|
||||
@Injectable()
|
||||
export class BlockchainQueryService {
|
||||
private readonly logger = new Logger(BlockchainQueryService.name);
|
||||
private readonly blockchainServiceUrl: string;
|
||||
|
||||
// 链的 decimals 配置
|
||||
private readonly chainDecimals: Record<string, number> = {
|
||||
KAVA: 6,
|
||||
BSC: 18,
|
||||
};
|
||||
|
||||
constructor(
|
||||
private readonly httpService: HttpService,
|
||||
private readonly configService: ConfigService,
|
||||
) {
|
||||
this.blockchainServiceUrl = this.configService.get<string>(
|
||||
'BLOCKCHAIN_SERVICE_URL',
|
||||
'http://blockchain-service:3000',
|
||||
);
|
||||
this.logger.log(`BlockchainQueryService initialized, URL: ${this.blockchainServiceUrl}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询单个地址的 USDT 余额
|
||||
*
|
||||
* @param chainType 链类型 (KAVA 或 BSC)
|
||||
* @param address EVM 地址
|
||||
*/
|
||||
async getUsdtBalance(chainType: string, address: string): Promise<UsdtBalance> {
|
||||
try {
|
||||
this.logger.debug(`查询 ${chainType} USDT 余额: ${address}`);
|
||||
|
||||
const response = await firstValueFrom(
|
||||
this.httpService.get<BlockchainBalanceResponse>(
|
||||
`${this.blockchainServiceUrl}/balance`,
|
||||
{
|
||||
params: {
|
||||
chainType,
|
||||
address,
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const data = response.data;
|
||||
this.logger.debug(`${chainType} USDT 余额: ${data.usdtBalance}`);
|
||||
|
||||
return {
|
||||
chainType: data.chainType,
|
||||
address: data.address,
|
||||
balance: data.usdtBalance,
|
||||
rawBalance: this.toRawBalance(data.usdtBalance, chainType),
|
||||
decimals: this.chainDecimals[chainType] || 6,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error(`查询 ${chainType} USDT 余额失败: ${error.message}`);
|
||||
throw new Error(`查询余额失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量查询多个地址的 USDT 余额
|
||||
*
|
||||
* @param addresses 地址列表 { chainType, address }[]
|
||||
*/
|
||||
async getMultipleUsdtBalances(
|
||||
addresses: Array<{ chainType: string; address: string }>,
|
||||
): Promise<UsdtBalance[]> {
|
||||
const results = await Promise.allSettled(
|
||||
addresses.map(({ chainType, address }) => this.getUsdtBalance(chainType, address)),
|
||||
);
|
||||
|
||||
return results.map((result, index) => {
|
||||
if (result.status === 'fulfilled') {
|
||||
return result.value;
|
||||
}
|
||||
// 失败时返回零余额
|
||||
this.logger.warn(`查询余额失败 [${addresses[index].chainType}:${addresses[index].address}]: ${result.reason}`);
|
||||
return {
|
||||
chainType: addresses[index].chainType,
|
||||
address: addresses[index].address,
|
||||
balance: '0',
|
||||
rawBalance: '0',
|
||||
decimals: this.chainDecimals[addresses[index].chainType] || 6,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询原生代币余额 (KAVA / BNB)
|
||||
*/
|
||||
async getNativeBalance(chainType: string, address: string): Promise<string> {
|
||||
try {
|
||||
const response = await firstValueFrom(
|
||||
this.httpService.get<BlockchainBalanceResponse>(
|
||||
`${this.blockchainServiceUrl}/balance`,
|
||||
{
|
||||
params: {
|
||||
chainType,
|
||||
address,
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
return response.data.nativeBalance;
|
||||
} catch (error) {
|
||||
this.logger.error(`查询 ${chainType} 原生代币余额失败: ${error.message}`);
|
||||
throw new Error(`查询余额失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将格式化余额转换为原始余额 (wei)
|
||||
*/
|
||||
private toRawBalance(formattedBalance: string, chainType: string): string {
|
||||
try {
|
||||
const decimals = this.chainDecimals[chainType] || 6;
|
||||
const [intPart, decPart = ''] = formattedBalance.split('.');
|
||||
const paddedDecPart = decPart.padEnd(decimals, '0').slice(0, decimals);
|
||||
const rawValue = BigInt(intPart + paddedDecPart);
|
||||
return rawValue.toString();
|
||||
} catch {
|
||||
return '0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import { Module, Global } from '@nestjs/common';
|
||||
import { HttpModule } from '@nestjs/axios';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { PrismaService } from './persistence/prisma/prisma.service';
|
||||
import { UserAccountRepositoryImpl } from './persistence/repositories/user-account.repository.impl';
|
||||
import { MpcKeyShareRepositoryImpl } from './persistence/repositories/mpc-key-share.repository.impl';
|
||||
|
|
@ -10,7 +9,6 @@ import { EventPublisherService } from './kafka/event-publisher.service';
|
|||
import { MpcEventConsumerService } from './kafka/mpc-event-consumer.service';
|
||||
import { SmsService } from './external/sms/sms.service';
|
||||
import { WalletGeneratorServiceImpl } from './external/blockchain/wallet-generator.service.impl';
|
||||
import { BlockchainQueryService } from './external/blockchain/blockchain-query.service';
|
||||
import { MpcClientService, MpcWalletService } from './external/mpc';
|
||||
import { MPC_KEY_SHARE_REPOSITORY } from '@/domain/repositories/mpc-key-share.repository.interface';
|
||||
import { WalletGeneratorService } from '@/domain/services/wallet-generator.service';
|
||||
|
|
@ -18,7 +16,6 @@ import { WalletGeneratorService } from '@/domain/services/wallet-generator.servi
|
|||
@Global()
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule,
|
||||
HttpModule.register({
|
||||
timeout: 300000,
|
||||
maxRedirects: 5,
|
||||
|
|
@ -42,7 +39,6 @@ import { WalletGeneratorService } from '@/domain/services/wallet-generator.servi
|
|||
provide: WalletGeneratorService,
|
||||
useExisting: WalletGeneratorServiceImpl,
|
||||
},
|
||||
BlockchainQueryService,
|
||||
MpcClientService,
|
||||
MpcWalletService,
|
||||
],
|
||||
|
|
@ -60,7 +56,6 @@ import { WalletGeneratorService } from '@/domain/services/wallet-generator.servi
|
|||
SmsService,
|
||||
WalletGeneratorServiceImpl,
|
||||
WalletGeneratorService,
|
||||
BlockchainQueryService,
|
||||
MpcClientService,
|
||||
MpcWalletService,
|
||||
],
|
||||
|
|
|
|||
Loading…
Reference in New Issue