diff --git a/backend/services/identity-service/src/infrastructure/external/blockchain/blockchain-query.service.ts b/backend/services/identity-service/src/infrastructure/external/blockchain/blockchain-query.service.ts index 36997f8e..0711a659 100644 --- a/backend/services/identity-service/src/infrastructure/external/blockchain/blockchain-query.service.ts +++ b/backend/services/identity-service/src/infrastructure/external/blockchain/blockchain-query.service.ts @@ -1,5 +1,7 @@ import { Injectable, Logger } from '@nestjs/common'; -import { ethers, JsonRpcProvider, Contract } from 'ethers'; +import { HttpService } from '@nestjs/axios'; +import { ConfigService } from '@nestjs/config'; +import { firstValueFrom } from 'rxjs'; /** * USDT 余额查询结果 @@ -13,75 +15,49 @@ export interface UsdtBalance { } /** - * 链配置 + * blockchain-service 余额响应 */ -interface ChainConfig { - rpcUrl: string; - usdtContract: string; - decimals: number; - name: string; +interface BlockchainBalanceResponse { + chainType: string; + address: string; + usdtBalance: string; + nativeBalance: string; + nativeSymbol: string; } /** - * ERC20 合约 ABI (仅需 balanceOf) + * blockchain-service 多链余额响应 */ -const ERC20_ABI = [ - 'function balanceOf(address owner) view returns (uint256)', - 'function decimals() view returns (uint8)', -]; +interface BlockchainMultiChainResponse { + address: string; + balances: BlockchainBalanceResponse[]; +} /** * 区块链查询服务 * - * 用于查询 KAVA EVM 和 BSC 链上的 USDT 余额 + * 通过 HTTP 调用 blockchain-service 查询余额 */ @Injectable() export class BlockchainQueryService { private readonly logger = new Logger(BlockchainQueryService.name); + private readonly blockchainServiceUrl: string; - /** - * 链配置 - */ - private readonly chainConfigs: Record = { - KAVA: { - rpcUrl: process.env.KAVA_RPC_URL || 'https://evm.kava.io', - // KAVA EVM 原生 USDT 合约地址 (Tether 官方发行) - usdtContract: '0x919C1c267BC06a7039e03fcc2eF738525769109c', - decimals: 6, - name: 'KAVA EVM', - }, - BSC: { - rpcUrl: process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org', - // BSC USDT 合约地址 (Binance-Peg) - usdtContract: '0x55d398326f99059fF775485246999027B3197955', - decimals: 18, - name: 'BSC', - }, + // 链的 decimals 配置 + private readonly chainDecimals: Record = { + KAVA: 6, + BSC: 18, }; - /** - * Provider 缓存 - */ - private providers: Map = new Map(); - - /** - * 获取或创建 Provider - */ - private getProvider(chainType: string): JsonRpcProvider { - if (this.providers.has(chainType)) { - return this.providers.get(chainType)!; - } - - const config = this.chainConfigs[chainType]; - if (!config) { - throw new Error(`不支持的链类型: ${chainType}`); - } - - const provider = new JsonRpcProvider(config.rpcUrl, undefined, { - staticNetwork: true, - }); - this.providers.set(chainType, provider); - return provider; + constructor( + private readonly httpService: HttpService, + private readonly configService: ConfigService, + ) { + this.blockchainServiceUrl = this.configService.get( + 'BLOCKCHAIN_SERVICE_URL', + 'http://blockchain-service:3000', + ); + this.logger.log(`BlockchainQueryService initialized, URL: ${this.blockchainServiceUrl}`); } /** @@ -91,36 +67,33 @@ export class BlockchainQueryService { * @param address EVM 地址 */ async getUsdtBalance(chainType: string, address: string): Promise { - const config = this.chainConfigs[chainType]; - if (!config) { - throw new Error(`不支持的链类型: ${chainType}`); - } - - // 验证地址格式 - if (!ethers.isAddress(address)) { - throw new Error(`无效的 EVM 地址: ${address}`); - } - try { - this.logger.debug(`查询 ${config.name} USDT 余额: ${address}`); + this.logger.debug(`查询 ${chainType} USDT 余额: ${address}`); - const provider = this.getProvider(chainType); - const contract = new Contract(config.usdtContract, ERC20_ABI, provider); + const response = await firstValueFrom( + this.httpService.get( + `${this.blockchainServiceUrl}/balance`, + { + params: { + chainType, + address, + }, + }, + ), + ); - const rawBalance = await contract.balanceOf(address); - const balance = ethers.formatUnits(rawBalance, config.decimals); - - this.logger.debug(`${config.name} USDT 余额: ${balance}`); + const data = response.data; + this.logger.debug(`${chainType} USDT 余额: ${data.usdtBalance}`); return { - chainType, - address, - balance, - rawBalance: rawBalance.toString(), - decimals: config.decimals, + 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(`查询 ${config.name} USDT 余额失败: ${error.message}`); + this.logger.error(`查询 ${chainType} USDT 余额失败: ${error.message}`); throw new Error(`查询余额失败: ${error.message}`); } } @@ -148,7 +121,7 @@ export class BlockchainQueryService { address: addresses[index].address, balance: '0', rawBalance: '0', - decimals: this.chainConfigs[addresses[index].chainType]?.decimals || 6, + decimals: this.chainDecimals[addresses[index].chainType] || 6, }; }); } @@ -157,22 +130,38 @@ export class BlockchainQueryService { * 查询原生代币余额 (KAVA / BNB) */ async getNativeBalance(chainType: string, address: string): Promise { - const config = this.chainConfigs[chainType]; - if (!config) { - throw new Error(`不支持的链类型: ${chainType}`); - } - - if (!ethers.isAddress(address)) { - throw new Error(`无效的 EVM 地址: ${address}`); - } - try { - const provider = this.getProvider(chainType); - const rawBalance = await provider.getBalance(address); - return ethers.formatEther(rawBalance); + const response = await firstValueFrom( + this.httpService.get( + `${this.blockchainServiceUrl}/balance`, + { + params: { + chainType, + address, + }, + }, + ), + ); + + return response.data.nativeBalance; } catch (error) { - this.logger.error(`查询 ${config.name} 原生代币余额失败: ${error.message}`); + 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'; + } + } } diff --git a/backend/services/identity-service/src/infrastructure/infrastructure.module.ts b/backend/services/identity-service/src/infrastructure/infrastructure.module.ts index a63130ef..fd9fc3de 100644 --- a/backend/services/identity-service/src/infrastructure/infrastructure.module.ts +++ b/backend/services/identity-service/src/infrastructure/infrastructure.module.ts @@ -1,5 +1,6 @@ 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'; @@ -17,6 +18,7 @@ import { WalletGeneratorService } from '@/domain/services/wallet-generator.servi @Global() @Module({ imports: [ + ConfigModule, HttpModule.register({ timeout: 300000, maxRedirects: 5,