/** * MPC Signing Client * * 直接调用 mpc-system 的 account-service (port 4000) 进行 MPC 签名 * 用于热钱包和做市商钱包的 ERC20 转账签名 * * 签名流程: * 1. POST /api/v1/mpc/sign → 创建签名会话 * 2. GET /api/v1/mpc/sessions/{session_id} → 轮询签名结果 */ import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { HttpService } from '@nestjs/axios'; import { JwtService } from '@nestjs/jwt'; import { randomUUID } from 'crypto'; import { firstValueFrom } from 'rxjs'; export interface CreateSigningInput { username: string; messageHash: string; } export interface SigningResult { sessionId: string; status: string; signature?: string; } // MPC 签名请求 Topic (保留导出以避免外部引用报错) export const MPC_SIGNING_TOPIC = 'mining_mpc.SigningRequested'; @Injectable() export class MpcSigningClient { private readonly logger = new Logger(MpcSigningClient.name); // C2C Bot 热钱包 private readonly hotWalletUsername: string; private readonly hotWalletAddress: string; // eUSDT (积分股) 做市商钱包 private readonly eusdtMarketMakerUsername: string; private readonly eusdtMarketMakerAddress: string; // fUSDT (积分值) 做市商钱包 private readonly fusdtMarketMakerUsername: string; private readonly fusdtMarketMakerAddress: string; // 100亿销毁池钱包 private readonly burnPoolUsername: string; private readonly burnPoolAddress: string; // 200万挖矿池钱包 private readonly miningPoolUsername: string; private readonly miningPoolAddress: string; // MPC system 配置 private readonly mpcAccountServiceUrl: string; private readonly mpcJwtSecret: string; private readonly signingTimeoutMs: number = 300000; // 5 minutes private readonly pollingIntervalMs: number = 2000; // 2 seconds constructor( private readonly configService: ConfigService, private readonly httpService: HttpService, private readonly jwtService: JwtService, ) { // C2C Bot 热钱包配置 this.hotWalletUsername = this.configService.get('C2C_BOT_WALLET_USERNAME', ''); this.hotWalletAddress = this.configService.get('C2C_BOT_WALLET_ADDRESS', ''); // eUSDT (积分股) 做市商钱包配置 this.eusdtMarketMakerUsername = this.configService.get('EUSDT_MARKET_MAKER_USERNAME', ''); this.eusdtMarketMakerAddress = this.configService.get('EUSDT_MARKET_MAKER_ADDRESS', ''); // fUSDT (积分值) 做市商钱包配置 this.fusdtMarketMakerUsername = this.configService.get('FUSDT_MARKET_MAKER_USERNAME', ''); this.fusdtMarketMakerAddress = this.configService.get('FUSDT_MARKET_MAKER_ADDRESS', ''); // 100亿销毁池钱包配置 this.burnPoolUsername = this.configService.get('BURN_POOL_WALLET_USERNAME', ''); this.burnPoolAddress = this.configService.get('BURN_POOL_WALLET_ADDRESS', ''); // 200万挖矿池钱包配置 this.miningPoolUsername = this.configService.get('MINING_POOL_WALLET_USERNAME', ''); this.miningPoolAddress = this.configService.get('MINING_POOL_WALLET_ADDRESS', ''); // MPC system 配置 this.mpcAccountServiceUrl = this.configService.get('MPC_ACCOUNT_SERVICE_URL', 'http://localhost:4000'); this.mpcJwtSecret = this.configService.get('MPC_JWT_SECRET', ''); if (!this.hotWalletUsername) { this.logger.warn('[INIT] C2C_BOT_WALLET_USERNAME not configured (C2C Bot disabled)'); } if (!this.hotWalletAddress) { this.logger.warn('[INIT] C2C_BOT_WALLET_ADDRESS not configured (C2C Bot disabled)'); } if (!this.eusdtMarketMakerUsername || !this.eusdtMarketMakerAddress) { this.logger.warn('[INIT] eUSDT Market Maker not configured'); } if (!this.fusdtMarketMakerUsername || !this.fusdtMarketMakerAddress) { this.logger.warn('[INIT] fUSDT Market Maker not configured'); } if (!this.burnPoolUsername || !this.burnPoolAddress) { this.logger.warn('[INIT] Burn Pool wallet not configured'); } if (!this.miningPoolUsername || !this.miningPoolAddress) { this.logger.warn('[INIT] Mining Pool wallet not configured'); } if (!this.mpcJwtSecret) { this.logger.warn('[INIT] MPC_JWT_SECRET not configured - signing will fail'); } this.logger.log(`[INIT] C2C Bot Wallet: ${this.hotWalletAddress || '(not configured)'}`); this.logger.log(`[INIT] eUSDT Market Maker: ${this.eusdtMarketMakerAddress || '(not configured)'}`); this.logger.log(`[INIT] fUSDT Market Maker: ${this.fusdtMarketMakerAddress || '(not configured)'}`); this.logger.log(`[INIT] Burn Pool: ${this.burnPoolAddress || '(not configured)'}`); this.logger.log(`[INIT] Mining Pool: ${this.miningPoolAddress || '(not configured)'}`); this.logger.log(`[INIT] MPC Account Service: ${this.mpcAccountServiceUrl}`); this.logger.log(`[INIT] Using HTTP direct call to mpc-system`); } /** * 检查 C2C Bot 热钱包是否已配置 */ isConfigured(): boolean { return !!this.hotWalletUsername && !!this.hotWalletAddress; } /** * 检查 eUSDT 做市商钱包是否已配置 */ isEusdtMarketMakerConfigured(): boolean { return !!this.eusdtMarketMakerUsername && !!this.eusdtMarketMakerAddress; } /** * 检查 fUSDT 做市商钱包是否已配置 */ isFusdtMarketMakerConfigured(): boolean { return !!this.fusdtMarketMakerUsername && !!this.fusdtMarketMakerAddress; } /** * 获取 C2C Bot 热钱包地址 */ getHotWalletAddress(): string { return this.hotWalletAddress; } /** * 获取 C2C Bot 热钱包用户名 */ getHotWalletUsername(): string { return this.hotWalletUsername; } /** * 获取 eUSDT 做市商钱包地址 */ getEusdtMarketMakerAddress(): string { return this.eusdtMarketMakerAddress; } /** * 获取 eUSDT 做市商 MPC 用户名 */ getEusdtMarketMakerUsername(): string { return this.eusdtMarketMakerUsername; } /** * 获取 fUSDT 做市商钱包地址 */ getFusdtMarketMakerAddress(): string { return this.fusdtMarketMakerAddress; } /** * 获取 fUSDT 做市商 MPC 用户名 */ getFusdtMarketMakerUsername(): string { return this.fusdtMarketMakerUsername; } // ============ 100亿销毁池钱包 ============ isBurnPoolConfigured(): boolean { return !!this.burnPoolUsername && !!this.burnPoolAddress; } getBurnPoolAddress(): string { return this.burnPoolAddress; } getBurnPoolUsername(): string { return this.burnPoolUsername; } async signMessageAsBurnPool(messageHash: string): Promise { if (!this.burnPoolUsername) { throw new Error('Burn Pool MPC username not configured'); } return this.signMessageWithUsername(this.burnPoolUsername, messageHash); } // ============ 200万挖矿池钱包 ============ isMiningPoolConfigured(): boolean { return !!this.miningPoolUsername && !!this.miningPoolAddress; } getMiningPoolAddress(): string { return this.miningPoolAddress; } getMiningPoolUsername(): string { return this.miningPoolUsername; } async signMessageAsMiningPool(messageHash: string): Promise { if (!this.miningPoolUsername) { throw new Error('Mining Pool MPC username not configured'); } return this.signMessageWithUsername(this.miningPoolUsername, messageHash); } /** * 签名消息(使用 C2C Bot 热钱包) * * @param messageHash 要签名的消息哈希 (hex string with 0x prefix) * @returns 签名结果 (hex string) */ async signMessage(messageHash: string): Promise { if (!this.hotWalletUsername) { throw new Error('Hot wallet username not configured'); } return this.signMessageWithUsername(this.hotWalletUsername, messageHash); } /** * 使用 eUSDT 做市商钱包签名消息 * * @param messageHash 要签名的消息哈希 (hex string with 0x prefix) * @returns 签名结果 (hex string) */ async signMessageAsEusdtMarketMaker(messageHash: string): Promise { if (!this.eusdtMarketMakerUsername) { throw new Error('eUSDT Market Maker MPC username not configured'); } return this.signMessageWithUsername(this.eusdtMarketMakerUsername, messageHash); } /** * 使用 fUSDT 做市商钱包签名消息 * * @param messageHash 要签名的消息哈希 (hex string with 0x prefix) * @returns 签名结果 (hex string) */ async signMessageAsFusdtMarketMaker(messageHash: string): Promise { if (!this.fusdtMarketMakerUsername) { throw new Error('fUSDT Market Maker MPC username not configured'); } return this.signMessageWithUsername(this.fusdtMarketMakerUsername, messageHash); } /** * 使用指定用户名签名消息(HTTP 直调 mpc-system) * * @param username MPC 用户名 * @param messageHash 要签名的消息哈希 (hex string with 0x prefix) * @returns 签名结果 (hex string) */ async signMessageWithUsername(username: string, messageHash: string): Promise { this.logger.log(`[SIGN] Starting MPC signing for: ${messageHash.slice(0, 16)}... (username: ${username})`); if (!username) { throw new Error('MPC username not provided'); } if (!this.mpcJwtSecret) { throw new Error('MPC_JWT_SECRET not configured'); } // Step 1: 创建签名会话 const createUrl = `${this.mpcAccountServiceUrl}/api/v1/mpc/sign`; const headers = this.getMpcAuthHeaders(); this.logger.log(`[SIGN] POST ${createUrl}`); const createResponse = await firstValueFrom( this.httpService.post<{ session_id: string; status: string; session_type?: string; username?: string; message_hash?: string; }>( createUrl, { username, message_hash: messageHash.startsWith('0x') ? messageHash.slice(2) : messageHash }, { headers, timeout: 30000 }, ), ); const sessionId = createResponse.data.session_id; this.logger.log(`[SIGN] Session created: ${sessionId}, status: ${createResponse.data.status}`); // Step 2: 轮询签名结果 const signature = await this.pollSigningStatus(sessionId); this.logger.log(`[SIGN] Signature obtained: ${signature.slice(0, 20)}...`); return signature; } /** * 轮询签名会话状态直到完成或超时 */ private async pollSigningStatus(sessionId: string): Promise { const statusUrl = `${this.mpcAccountServiceUrl}/api/v1/mpc/sessions/${sessionId}`; const maxAttempts = Math.ceil(this.signingTimeoutMs / this.pollingIntervalMs); for (let attempt = 1; attempt <= maxAttempts; attempt++) { await this.delay(this.pollingIntervalMs); const response = await firstValueFrom( this.httpService.get<{ session_id: string; status: string; session_type?: string; completed_parties?: number; total_parties?: number; signature?: string; }>(statusUrl, { headers: this.getMpcAuthHeaders(), timeout: 10000, }), ); const { status, signature } = response.data; if (attempt % 5 === 0 || status !== 'pending') { this.logger.log(`[POLL] Attempt ${attempt}/${maxAttempts}: status=${status}`); } if (status === 'completed') { if (!signature) { throw new Error('Signing completed but no signature returned'); } return signature; } if (status === 'failed' || status === 'expired') { throw new Error(`MPC signing ${status}: sessionId=${sessionId}`); } } throw new Error(`MPC signing timeout after ${this.signingTimeoutMs}ms`); } /** * 生成 mpc-system 认证 JWT token */ private generateMpcAccessToken(): string { const now = Math.floor(Date.now() / 1000); const payload = { jti: randomUUID(), iss: 'mining-blockchain-service', sub: 'system', username: 'mining-blockchain-service', token_type: 'access', iat: now, nbf: now, exp: now + 24 * 60 * 60, }; return this.jwtService.sign(payload, { secret: this.mpcJwtSecret, algorithm: 'HS256' as const, }); } /** * 获取 mpc-system 认证请求头 */ private getMpcAuthHeaders(): Record { const token = this.generateMpcAccessToken(); return { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }; } private delay(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } }