rwadurian/backend/services/mining-admin-service/src/api/controllers/pool-account.controller.ts

268 lines
9.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<string, PoolMapping>;
constructor(private readonly configService: ConfigService) {
this.walletServiceUrl = this.configService.get<string>(
'MINING_WALLET_SERVICE_URL',
'http://localhost:3025',
);
this.blockchainServiceUrl = this.configService.get<string>(
'MINING_BLOCKCHAIN_SERVICE_URL',
'http://localhost:3020',
);
// 从环境变量构建 walletName → poolType 映射
const burnPoolUsername = this.configService.get<string>('BURN_POOL_WALLET_USERNAME', '');
const miningPoolUsername = this.configService.get<string>('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,
);
}
}
}