import { Controller, Get, Post, Body, Param, Req, Logger, BadRequestException, HttpException, HttpStatus } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiBearerAuth, ApiParam } from '@nestjs/swagger'; import { ConfigService } from '@nestjs/config'; // walletName → poolType 映射 interface PoolMapping { blockchainPoolType: string; // blockchain-service 使用的类型 (BURN_POOL / MINING_POOL) walletPoolType: string; // wallet-service 使用的类型 (SHARE_POOL_A / SHARE_POOL_B) name: string; } @ApiTags('Pool Accounts') @ApiBearerAuth() @Controller('admin/pool-accounts') export class PoolAccountController { private readonly logger = new Logger(PoolAccountController.name); private readonly walletServiceUrl: string; private readonly blockchainServiceUrl: string; private readonly walletNameMap: Record; constructor(private readonly configService: ConfigService) { this.walletServiceUrl = this.configService.get( 'MINING_WALLET_SERVICE_URL', 'http://localhost:3025', ); this.blockchainServiceUrl = this.configService.get( 'MINING_BLOCKCHAIN_SERVICE_URL', 'http://localhost:3020', ); // 从环境变量构建 walletName → poolType 映射 const burnPoolUsername = this.configService.get('BURN_POOL_WALLET_USERNAME', ''); const miningPoolUsername = this.configService.get('MINING_POOL_WALLET_USERNAME', ''); this.walletNameMap = {}; if (burnPoolUsername) { this.walletNameMap[burnPoolUsername] = { blockchainPoolType: 'BURN_POOL', walletPoolType: 'SHARE_POOL_A', name: '100亿销毁池', }; } if (miningPoolUsername) { this.walletNameMap[miningPoolUsername] = { blockchainPoolType: 'MINING_POOL', walletPoolType: 'SHARE_POOL_B', name: '200万挖矿池', }; } this.logger.log( `Pool account proxy initialized: walletService=${this.walletServiceUrl}, blockchainService=${this.blockchainServiceUrl}, pools=${Object.keys(this.walletNameMap).join(',')}`, ); } @Get() @ApiOperation({ summary: '获取已配置的池账户列表' }) async listPoolAccounts() { return Object.entries(this.walletNameMap).map(([walletName, info]) => ({ walletName, name: info.name, walletPoolType: info.walletPoolType, blockchainPoolType: info.blockchainPoolType, })); } @Get(':walletName/balance') @ApiOperation({ summary: '获取池账户余额(代理到 wallet-service + blockchain-service)' }) @ApiParam({ name: 'walletName', description: '池钱包名称(MPC用户名)' }) async getPoolAccountBalance(@Param('walletName') walletName: string) { const poolInfo = this.walletNameMap[walletName]; if (!poolInfo) { throw new BadRequestException(`Unknown wallet name: ${walletName}`); } // 并行调用 wallet-service 和 blockchain-service const [walletResponse, blockchainResponse] = await Promise.all([ fetch(`${this.walletServiceUrl}/api/v2/pool-accounts/${poolInfo.walletPoolType}`).catch((err) => { this.logger.error(`Failed to fetch wallet balance: ${err.message}`); return null; }), fetch(`${this.blockchainServiceUrl}/api/v1/transfer/pool-accounts/${poolInfo.blockchainPoolType}/wallet-info`).catch((err) => { this.logger.error(`Failed to fetch blockchain wallet info: ${err.message}`); return null; }), ]); // 从 wallet-service 获取链下余额 let balance = '0'; let lastUpdated: string | undefined; if (walletResponse && walletResponse.ok) { const walletResult = await walletResponse.json(); const data = walletResult.data || walletResult; balance = data.balance?.toString() || '0'; lastUpdated = data.updatedAt; } // 从 blockchain-service 获取钱包地址和链上余额 let walletAddress = ''; let onChainBalance = '0'; if (blockchainResponse && blockchainResponse.ok) { const blockchainResult = await blockchainResponse.json(); const data = blockchainResult.data || blockchainResult; walletAddress = data.address || ''; onChainBalance = data.balance || '0'; } return { walletName, walletAddress, balance, availableBalance: balance, frozenBalance: '0', onChainBalance, threshold: '0', lastUpdated, }; } @Post(':walletName/blockchain-withdraw') @ApiOperation({ summary: '池账户区块链提现(代理到 blockchain-service + wallet-service)' }) @ApiParam({ name: 'walletName', description: '池钱包名称(MPC用户名)' }) async poolAccountBlockchainWithdraw( @Param('walletName') walletName: string, @Body() body: { toAddress: string; amount: string }, ) { const poolInfo = this.walletNameMap[walletName]; if (!poolInfo) { throw new BadRequestException(`Unknown wallet name: ${walletName}`); } if (!body.toAddress || !body.amount) { throw new BadRequestException('toAddress and amount are required'); } this.logger.log( `[withdraw] ${poolInfo.name}: ${body.amount} fUSDT to ${body.toAddress}`, ); // Step 1: 通过 blockchain-service 执行区块链转账 let blockchainData: any; try { const blockchainResponse = await fetch( `${this.blockchainServiceUrl}/api/v1/transfer/pool-account`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ poolType: poolInfo.blockchainPoolType, toAddress: body.toAddress, amount: body.amount, }), }, ); const blockchainResult = await blockchainResponse.json(); blockchainData = blockchainResult.data || blockchainResult; if (!blockchainResponse.ok || !blockchainData.success) { this.logger.error(`[withdraw] Blockchain transfer failed: ${blockchainData.error}`); throw new HttpException( blockchainData.error || '区块链转账失败', blockchainResponse.status || HttpStatus.INTERNAL_SERVER_ERROR, ); } } catch (error) { if (error instanceof HttpException) throw error; this.logger.error(`[withdraw] Failed to call blockchain service: ${error}`); throw new HttpException( `区块链服务调用失败: ${error instanceof Error ? error.message : error}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } // Step 2: 在 wallet-service 记录提现(扣减余额 + 分类账) try { const walletResponse = await fetch( `${this.walletServiceUrl}/api/v2/pool-accounts/blockchain-withdraw`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ poolType: poolInfo.walletPoolType, amount: body.amount, txHash: blockchainData.txHash, toAddress: body.toAddress, blockNumber: blockchainData.blockNumber?.toString(), }), }, ); if (!walletResponse.ok) { const errResult = await walletResponse.json().catch(() => ({})); this.logger.error( `[withdraw] Failed to record withdrawal in wallet-service: ${JSON.stringify(errResult)}`, ); } } catch (error) { // 区块链转账已成功,wallet-service 记录失败不影响最终结果 this.logger.error(`[withdraw] Failed to record withdrawal in wallet-service: ${error}`); } this.logger.log( `[withdraw] Success: ${poolInfo.name}, txHash=${blockchainData.txHash}`, ); return { success: true, message: '提现成功', txHash: blockchainData.txHash, blockNumber: blockchainData.blockNumber, }; } @Post(':walletName/centralized-deposit') @ApiOperation({ summary: '池账户中心化充值(管理员手动调整余额)' }) @ApiParam({ name: 'walletName', description: '池钱包名称(MPC用户名)' }) async centralizedDeposit( @Param('walletName') walletName: string, @Body() body: { amount: string }, @Req() req: any, ) { const poolInfo = this.walletNameMap[walletName]; if (!poolInfo) { throw new BadRequestException(`Unknown wallet name: ${walletName}`); } if (!body.amount || parseFloat(body.amount) <= 0) { throw new BadRequestException('amount must be greater than 0'); } const adminId = req.admin?.id || 'admin'; this.logger.log(`[centralized-deposit] ${poolInfo.name}: ${body.amount} by admin ${adminId}`); try { const walletResponse = await fetch( `${this.walletServiceUrl}/api/v2/pool-accounts/centralized-deposit`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ poolType: poolInfo.walletPoolType, amount: body.amount, adminId: String(adminId), }), }, ); if (!walletResponse.ok) { const errResult = await walletResponse.json().catch(() => ({})); throw new HttpException( errResult.message || '中心化充值失败', walletResponse.status || HttpStatus.INTERNAL_SERVER_ERROR, ); } return { success: true, message: `${poolInfo.name}充值成功` }; } catch (error) { if (error instanceof HttpException) throw error; this.logger.error(`[centralized-deposit] Failed: ${error}`); throw new HttpException( `中心化充值失败: ${error instanceof Error ? error.message : error}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } } }