feat(mining-blockchain-service): 支持做市商独立MPC钱包签名
- MpcSigningClient 支持两个钱包: C2C Bot 和做市商 - HOT_WALLET_USERNAME/ADDRESS: C2C Bot 热钱包 - MARKET_MAKER_MPC_USERNAME/WALLET_ADDRESS: 做市商钱包 - Erc20TransferService 新增 transferTokenAsMarketMaker() 方法 - eUSDT/fUSDT 转账使用做市商钱包签名和转账 - 新增 /transfer/market-maker/status 状态检查接口 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
4283a369ae
commit
cfdcd9352a
|
|
@ -112,15 +112,16 @@ export class TransferController {
|
|||
};
|
||||
}
|
||||
|
||||
// ============ eUSDT (积分股) 转账接口 ============
|
||||
// ============ eUSDT (积分股) 转账接口 - 使用做市商钱包 ============
|
||||
|
||||
@Post('eusdt')
|
||||
@ApiOperation({ summary: '转账 eUSDT(积分股)到指定地址' })
|
||||
@ApiOperation({ summary: '转账 eUSDT(积分股)到指定地址 - 使用做市商钱包' })
|
||||
@ApiResponse({ status: 200, description: '转账结果', type: TransferResponseDto })
|
||||
@ApiResponse({ status: 400, description: '参数错误' })
|
||||
@ApiResponse({ status: 500, description: '转账失败' })
|
||||
async transferEusdt(@Body() dto: TransferDusdtDto): Promise<TransferResponseDto> {
|
||||
const result: TransferResult = await this.erc20TransferService.transferToken(
|
||||
// eUSDT 使用做市商钱包转账
|
||||
const result: TransferResult = await this.erc20TransferService.transferTokenAsMarketMaker(
|
||||
ChainTypeEnum.KAVA,
|
||||
'EUSDT',
|
||||
dto.toAddress,
|
||||
|
|
@ -137,11 +138,11 @@ export class TransferController {
|
|||
}
|
||||
|
||||
@Get('eusdt/balance')
|
||||
@ApiOperation({ summary: '查询热钱包 eUSDT(积分股)余额' })
|
||||
@ApiOperation({ summary: '查询做市商钱包 eUSDT(积分股)余额' })
|
||||
@ApiResponse({ status: 200, description: '余额信息', type: BalanceResponseDto })
|
||||
async getEusdtBalance(): Promise<BalanceResponseDto> {
|
||||
const address = this.erc20TransferService.getHotWalletAddress(ChainTypeEnum.KAVA);
|
||||
const balance = await this.erc20TransferService.getTokenBalance(ChainTypeEnum.KAVA, 'EUSDT');
|
||||
const address = this.erc20TransferService.getMarketMakerAddress(ChainTypeEnum.KAVA);
|
||||
const balance = await this.erc20TransferService.getMarketMakerTokenBalance(ChainTypeEnum.KAVA, 'EUSDT');
|
||||
|
||||
return {
|
||||
address: address || '',
|
||||
|
|
@ -150,15 +151,16 @@ export class TransferController {
|
|||
};
|
||||
}
|
||||
|
||||
// ============ fUSDT (积分值) 转账接口 ============
|
||||
// ============ fUSDT (积分值) 转账接口 - 使用做市商钱包 ============
|
||||
|
||||
@Post('fusdt')
|
||||
@ApiOperation({ summary: '转账 fUSDT(积分值)到指定地址' })
|
||||
@ApiOperation({ summary: '转账 fUSDT(积分值)到指定地址 - 使用做市商钱包' })
|
||||
@ApiResponse({ status: 200, description: '转账结果', type: TransferResponseDto })
|
||||
@ApiResponse({ status: 400, description: '参数错误' })
|
||||
@ApiResponse({ status: 500, description: '转账失败' })
|
||||
async transferFusdt(@Body() dto: TransferDusdtDto): Promise<TransferResponseDto> {
|
||||
const result: TransferResult = await this.erc20TransferService.transferToken(
|
||||
// fUSDT 使用做市商钱包转账
|
||||
const result: TransferResult = await this.erc20TransferService.transferTokenAsMarketMaker(
|
||||
ChainTypeEnum.KAVA,
|
||||
'FUSDT',
|
||||
dto.toAddress,
|
||||
|
|
@ -175,11 +177,11 @@ export class TransferController {
|
|||
}
|
||||
|
||||
@Get('fusdt/balance')
|
||||
@ApiOperation({ summary: '查询热钱包 fUSDT(积分值)余额' })
|
||||
@ApiOperation({ summary: '查询做市商钱包 fUSDT(积分值)余额' })
|
||||
@ApiResponse({ status: 200, description: '余额信息', type: BalanceResponseDto })
|
||||
async getFusdtBalance(): Promise<BalanceResponseDto> {
|
||||
const address = this.erc20TransferService.getHotWalletAddress(ChainTypeEnum.KAVA);
|
||||
const balance = await this.erc20TransferService.getTokenBalance(ChainTypeEnum.KAVA, 'FUSDT');
|
||||
const address = this.erc20TransferService.getMarketMakerAddress(ChainTypeEnum.KAVA);
|
||||
const balance = await this.erc20TransferService.getMarketMakerTokenBalance(ChainTypeEnum.KAVA, 'FUSDT');
|
||||
|
||||
return {
|
||||
address: address || '',
|
||||
|
|
@ -187,4 +189,19 @@ export class TransferController {
|
|||
chain: 'KAVA',
|
||||
};
|
||||
}
|
||||
|
||||
// ============ 服务状态接口 ============
|
||||
|
||||
@Get('market-maker/status')
|
||||
@ApiOperation({ summary: '检查做市商转账服务状态' })
|
||||
@ApiResponse({ status: 200, description: '服务状态' })
|
||||
async getMarketMakerStatus(): Promise<{ configured: boolean; marketMakerAddress: string | null }> {
|
||||
const configured = this.erc20TransferService.isMarketMakerConfigured(ChainTypeEnum.KAVA);
|
||||
const marketMakerAddress = this.erc20TransferService.getMarketMakerAddress(ChainTypeEnum.KAVA);
|
||||
|
||||
return {
|
||||
configured,
|
||||
marketMakerAddress,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,9 +34,14 @@ export type TokenType = 'DUSDT' | 'EUSDT' | 'FUSDT';
|
|||
|
||||
// MPC 签名客户端接口(避免循环依赖)
|
||||
export interface IMpcSigningClient {
|
||||
// C2C Bot 热钱包
|
||||
isConfigured(): boolean;
|
||||
getHotWalletAddress(): string;
|
||||
signMessage(messageHash: string): Promise<string>;
|
||||
// 做市商钱包
|
||||
isMarketMakerConfigured(): boolean;
|
||||
getMarketMakerAddress(): string;
|
||||
signMessageAsMarketMaker(messageHash: string): Promise<string>;
|
||||
}
|
||||
|
||||
export const MPC_SIGNING_CLIENT = Symbol('MPC_SIGNING_CLIENT');
|
||||
|
|
@ -51,7 +56,10 @@ export const MPC_SIGNING_CLIENT = Symbol('MPC_SIGNING_CLIENT');
|
|||
export class Erc20TransferService {
|
||||
private readonly logger = new Logger(Erc20TransferService.name);
|
||||
private readonly providers: Map<ChainTypeEnum, JsonRpcProvider> = new Map();
|
||||
// C2C Bot 热钱包地址
|
||||
private readonly hotWalletAddress: string;
|
||||
// 做市商钱包地址
|
||||
private readonly marketMakerAddress: string;
|
||||
private mpcSigningClient: IMpcSigningClient | null = null;
|
||||
|
||||
constructor(
|
||||
|
|
@ -59,6 +67,7 @@ export class Erc20TransferService {
|
|||
private readonly chainConfig: ChainConfigService,
|
||||
) {
|
||||
this.hotWalletAddress = this.configService.get<string>('HOT_WALLET_ADDRESS', '');
|
||||
this.marketMakerAddress = this.configService.get<string>('MARKET_MAKER_WALLET_ADDRESS', '');
|
||||
this.initializeProviders();
|
||||
}
|
||||
|
||||
|
|
@ -85,20 +94,34 @@ export class Erc20TransferService {
|
|||
|
||||
// 检查热钱包地址配置
|
||||
if (this.hotWalletAddress) {
|
||||
this.logger.log(`[INIT] Hot wallet address configured: ${this.hotWalletAddress}`);
|
||||
this.logger.log(`[INIT] C2C Bot wallet address configured: ${this.hotWalletAddress}`);
|
||||
} else {
|
||||
this.logger.warn('[INIT] HOT_WALLET_ADDRESS not configured, transfers will fail');
|
||||
this.logger.warn('[INIT] HOT_WALLET_ADDRESS not configured, C2C transfers will fail');
|
||||
}
|
||||
|
||||
// 检查做市商钱包地址配置
|
||||
if (this.marketMakerAddress) {
|
||||
this.logger.log(`[INIT] Market Maker wallet address configured: ${this.marketMakerAddress}`);
|
||||
} else {
|
||||
this.logger.warn('[INIT] MARKET_MAKER_WALLET_ADDRESS not configured, Market Maker transfers will fail');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取热钱包地址
|
||||
* 获取 C2C Bot 热钱包地址
|
||||
*/
|
||||
getHotWalletAddress(chainType: ChainTypeEnum): string | null {
|
||||
// MPC 钱包地址在所有 EVM 链上相同
|
||||
return this.hotWalletAddress || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取做市商钱包地址
|
||||
*/
|
||||
getMarketMakerAddress(chainType: ChainTypeEnum): string | null {
|
||||
return this.marketMakerAddress || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取热钱包 USDT 余额
|
||||
*/
|
||||
|
|
@ -553,11 +576,237 @@ export class Erc20TransferService {
|
|||
}
|
||||
|
||||
/**
|
||||
* 检查热钱包是否已配置
|
||||
* 检查 C2C Bot 热钱包是否已配置
|
||||
*/
|
||||
isConfigured(chainType: ChainTypeEnum): boolean {
|
||||
return this.providers.has(chainType) &&
|
||||
!!this.hotWalletAddress &&
|
||||
!!this.mpcSigningClient?.isConfigured();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查做市商钱包是否已配置
|
||||
*/
|
||||
isMarketMakerConfigured(chainType: ChainTypeEnum): boolean {
|
||||
return this.providers.has(chainType) &&
|
||||
!!this.marketMakerAddress &&
|
||||
!!this.mpcSigningClient?.isMarketMakerConfigured();
|
||||
}
|
||||
|
||||
/**
|
||||
* 做市商代币转账(使用做市商 MPC 钱包签名)
|
||||
*
|
||||
* @param chainType 链类型 (KAVA, BSC)
|
||||
* @param tokenType 代币类型 (DUSDT, EUSDT, FUSDT)
|
||||
* @param toAddress 接收地址
|
||||
* @param amount 转账金额 (人类可读格式,如 "100.5")
|
||||
* @returns 转账结果
|
||||
*/
|
||||
async transferTokenAsMarketMaker(
|
||||
chainType: ChainTypeEnum,
|
||||
tokenType: TokenType,
|
||||
toAddress: string,
|
||||
amount: string,
|
||||
): Promise<TransferResult> {
|
||||
const tokenName = tokenType === 'EUSDT' ? '积分股' : tokenType === 'FUSDT' ? '积分值' : 'dUSDT';
|
||||
this.logger.log(`[MM-TRANSFER] Starting Market Maker ${tokenType} (${tokenName}) transfer`);
|
||||
this.logger.log(`[MM-TRANSFER] Chain: ${chainType}`);
|
||||
this.logger.log(`[MM-TRANSFER] From: ${this.marketMakerAddress}`);
|
||||
this.logger.log(`[MM-TRANSFER] To: ${toAddress}`);
|
||||
this.logger.log(`[MM-TRANSFER] Amount: ${amount} ${tokenType}`);
|
||||
|
||||
const provider = this.providers.get(chainType);
|
||||
if (!provider) {
|
||||
const error = `Provider not configured for chain: ${chainType}`;
|
||||
this.logger.error(`[MM-TRANSFER] ${error}`);
|
||||
return { success: false, error };
|
||||
}
|
||||
|
||||
if (!this.mpcSigningClient || !this.mpcSigningClient.isMarketMakerConfigured()) {
|
||||
const error = 'Market Maker MPC signing not configured';
|
||||
this.logger.error(`[MM-TRANSFER] ${error}`);
|
||||
return { success: false, error };
|
||||
}
|
||||
|
||||
if (!this.marketMakerAddress) {
|
||||
const error = 'Market Maker wallet address not configured';
|
||||
this.logger.error(`[MM-TRANSFER] ${error}`);
|
||||
return { success: false, error };
|
||||
}
|
||||
|
||||
try {
|
||||
const config = this.chainConfig.getConfig(ChainType.fromEnum(chainType));
|
||||
const contractAddress = this.getTokenContract(chainType, tokenType);
|
||||
|
||||
if (!contractAddress) {
|
||||
const error = `Token ${tokenType} not configured for chain ${chainType}`;
|
||||
this.logger.error(`[MM-TRANSFER] ${error}`);
|
||||
return { success: false, error };
|
||||
}
|
||||
|
||||
const contract = new Contract(contractAddress, ERC20_TRANSFER_ABI, provider);
|
||||
|
||||
// 获取代币精度
|
||||
const decimals = await contract.decimals();
|
||||
this.logger.log(`[MM-TRANSFER] Token decimals: ${decimals}`);
|
||||
|
||||
// 转换金额
|
||||
const amountInWei = parseUnits(amount, decimals);
|
||||
this.logger.log(`[MM-TRANSFER] Amount in wei: ${amountInWei.toString()}`);
|
||||
|
||||
// 检查余额
|
||||
const balance = await contract.balanceOf(this.marketMakerAddress);
|
||||
this.logger.log(`[MM-TRANSFER] Market Maker balance: ${formatUnits(balance, decimals)} ${tokenType}`);
|
||||
|
||||
if (balance < amountInWei) {
|
||||
const error = `Insufficient ${tokenType} balance in Market Maker wallet`;
|
||||
this.logger.error(`[MM-TRANSFER] ${error}`);
|
||||
return { success: false, error };
|
||||
}
|
||||
|
||||
// 构建交易
|
||||
this.logger.log(`[MM-TRANSFER] Building transaction...`);
|
||||
const nonce = await provider.getTransactionCount(this.marketMakerAddress);
|
||||
const feeData = await provider.getFeeData();
|
||||
|
||||
// ERC20 transfer 的 calldata
|
||||
const transferData = contract.interface.encodeFunctionData('transfer', [toAddress, amountInWei]);
|
||||
|
||||
// 估算 gas
|
||||
const gasEstimate = await provider.estimateGas({
|
||||
from: this.marketMakerAddress,
|
||||
to: contractAddress,
|
||||
data: transferData,
|
||||
});
|
||||
|
||||
const gasLimit = gasEstimate * BigInt(120) / BigInt(100); // 增加 20% buffer
|
||||
|
||||
// 检测链是否支持 EIP-1559
|
||||
const supportsEip1559 = feeData.maxFeePerGas && feeData.maxFeePerGas > BigInt(0);
|
||||
|
||||
let tx: Transaction;
|
||||
if (supportsEip1559) {
|
||||
tx = Transaction.from({
|
||||
type: 2,
|
||||
chainId: config.chainId,
|
||||
nonce,
|
||||
to: contractAddress,
|
||||
data: transferData,
|
||||
gasLimit,
|
||||
maxFeePerGas: feeData.maxFeePerGas,
|
||||
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
|
||||
});
|
||||
} else {
|
||||
const gasPrice = feeData.gasPrice || BigInt(1000000000);
|
||||
tx = Transaction.from({
|
||||
type: 0,
|
||||
chainId: config.chainId,
|
||||
nonce,
|
||||
to: contractAddress,
|
||||
data: transferData,
|
||||
gasLimit,
|
||||
gasPrice,
|
||||
});
|
||||
}
|
||||
|
||||
this.logger.log(`[MM-TRANSFER] Transaction built: nonce=${nonce}, gasLimit=${tx.gasLimit}`);
|
||||
|
||||
// 获取交易哈希用于签名
|
||||
const unsignedTxHash = tx.unsignedHash;
|
||||
this.logger.log(`[MM-TRANSFER] Unsigned tx hash: ${unsignedTxHash}`);
|
||||
|
||||
// 使用做市商 MPC 钱包签名
|
||||
this.logger.log(`[MM-TRANSFER] Requesting Market Maker MPC signature...`);
|
||||
const signatureHex = await this.mpcSigningClient.signMessageAsMarketMaker(unsignedTxHash);
|
||||
this.logger.log(`[MM-TRANSFER] MPC signature obtained: ${signatureHex.slice(0, 20)}...`);
|
||||
|
||||
// 解析签名
|
||||
const normalizedSig = signatureHex.startsWith('0x') ? signatureHex : `0x${signatureHex}`;
|
||||
const sigBytes = normalizedSig.slice(2);
|
||||
const r = `0x${sigBytes.slice(0, 64)}`;
|
||||
const s = `0x${sigBytes.slice(64, 128)}`;
|
||||
|
||||
// 尝试 yParity 0 和 1 来找到正确的 recovery id
|
||||
let signature: Signature | null = null;
|
||||
for (const yParity of [0, 1] as const) {
|
||||
try {
|
||||
const testSig = Signature.from({ r, s, yParity });
|
||||
const recoveredAddress = recoverAddress(unsignedTxHash, testSig);
|
||||
|
||||
if (recoveredAddress.toLowerCase() === this.marketMakerAddress.toLowerCase()) {
|
||||
this.logger.log(`[MM-TRANSFER] Found correct yParity: ${yParity}`);
|
||||
signature = testSig;
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.debug(`[MM-TRANSFER] yParity=${yParity} failed: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!signature) {
|
||||
throw new Error('Failed to recover correct signature - address mismatch');
|
||||
}
|
||||
|
||||
// 创建已签名交易
|
||||
const signedTx = tx.clone();
|
||||
signedTx.signature = signature;
|
||||
|
||||
// 广播交易
|
||||
this.logger.log(`[MM-TRANSFER] Broadcasting transaction...`);
|
||||
const txResponse = await provider.broadcastTransaction(signedTx.serialized);
|
||||
this.logger.log(`[MM-TRANSFER] Transaction sent: ${txResponse.hash}`);
|
||||
|
||||
// 等待确认
|
||||
this.logger.log(`[MM-TRANSFER] Waiting for confirmation...`);
|
||||
const receipt = await txResponse.wait();
|
||||
|
||||
if (receipt && receipt.status === 1) {
|
||||
this.logger.log(`[MM-TRANSFER] Transaction confirmed!`);
|
||||
this.logger.log(`[MM-TRANSFER] Block: ${receipt.blockNumber}`);
|
||||
this.logger.log(`[MM-TRANSFER] Gas used: ${receipt.gasUsed.toString()}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
txHash: txResponse.hash,
|
||||
gasUsed: receipt.gasUsed.toString(),
|
||||
blockNumber: receipt.blockNumber,
|
||||
};
|
||||
} else {
|
||||
const error = 'Transaction failed (reverted)';
|
||||
this.logger.error(`[MM-TRANSFER] ${error}`);
|
||||
return { success: false, txHash: txResponse.hash, error };
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.logger.error(`[MM-TRANSFER] Transfer failed:`, error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message || 'Unknown error during transfer',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取做市商指定代币余额
|
||||
*/
|
||||
async getMarketMakerTokenBalance(chainType: ChainTypeEnum, tokenType: TokenType): Promise<string> {
|
||||
const provider = this.providers.get(chainType);
|
||||
if (!provider) {
|
||||
throw new Error(`Provider not configured for chain: ${chainType}`);
|
||||
}
|
||||
|
||||
if (!this.marketMakerAddress) {
|
||||
throw new Error('Market Maker wallet address not configured');
|
||||
}
|
||||
|
||||
const contractAddress = this.getTokenContract(chainType, tokenType);
|
||||
if (!contractAddress) {
|
||||
throw new Error(`Token ${tokenType} not configured for chain ${chainType}`);
|
||||
}
|
||||
|
||||
const contract = new Contract(contractAddress, ERC20_TRANSFER_ABI, provider);
|
||||
const balance = await contract.balanceOf(this.marketMakerAddress);
|
||||
const decimals = await contract.decimals();
|
||||
|
||||
return formatUnits(balance, decimals);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,8 +39,12 @@ export const MPC_SIGNING_TOPIC = 'mpc.SigningRequested';
|
|||
@Injectable()
|
||||
export class MpcSigningClient implements OnModuleInit {
|
||||
private readonly logger = new Logger(MpcSigningClient.name);
|
||||
// C2C Bot 热钱包
|
||||
private readonly hotWalletUsername: string;
|
||||
private readonly hotWalletAddress: string;
|
||||
// 做市商 MPC 钱包
|
||||
private readonly marketMakerUsername: string;
|
||||
private readonly marketMakerAddress: string;
|
||||
private readonly signingTimeoutMs: number = 300000; // 5 minutes
|
||||
|
||||
// 待处理的签名请求回调 Map<sessionId, { resolve, reject, timeout }>
|
||||
|
|
@ -55,18 +59,28 @@ export class MpcSigningClient implements OnModuleInit {
|
|||
private readonly eventPublisher: EventPublisherService,
|
||||
private readonly mpcEventConsumer: MpcEventConsumerService,
|
||||
) {
|
||||
// C2C Bot 热钱包配置
|
||||
this.hotWalletUsername = this.configService.get<string>('HOT_WALLET_USERNAME', '');
|
||||
this.hotWalletAddress = this.configService.get<string>('HOT_WALLET_ADDRESS', '');
|
||||
// 做市商 MPC 钱包配置
|
||||
this.marketMakerUsername = this.configService.get<string>('MARKET_MAKER_MPC_USERNAME', '');
|
||||
this.marketMakerAddress = this.configService.get<string>('MARKET_MAKER_WALLET_ADDRESS', '');
|
||||
|
||||
if (!this.hotWalletUsername) {
|
||||
this.logger.warn('[INIT] HOT_WALLET_USERNAME not configured');
|
||||
this.logger.warn('[INIT] HOT_WALLET_USERNAME not configured (C2C Bot disabled)');
|
||||
}
|
||||
if (!this.hotWalletAddress) {
|
||||
this.logger.warn('[INIT] HOT_WALLET_ADDRESS not configured');
|
||||
this.logger.warn('[INIT] HOT_WALLET_ADDRESS not configured (C2C Bot disabled)');
|
||||
}
|
||||
if (!this.marketMakerUsername) {
|
||||
this.logger.warn('[INIT] MARKET_MAKER_MPC_USERNAME not configured (Market Maker signing disabled)');
|
||||
}
|
||||
if (!this.marketMakerAddress) {
|
||||
this.logger.warn('[INIT] MARKET_MAKER_WALLET_ADDRESS not configured (Market Maker disabled)');
|
||||
}
|
||||
|
||||
this.logger.log(`[INIT] Hot Wallet Username: ${this.hotWalletUsername || '(not configured)'}`);
|
||||
this.logger.log(`[INIT] Hot Wallet Address: ${this.hotWalletAddress || '(not configured)'}`);
|
||||
this.logger.log(`[INIT] C2C Bot Wallet: ${this.hotWalletAddress || '(not configured)'}`);
|
||||
this.logger.log(`[INIT] Market Maker Wallet: ${this.marketMakerAddress || '(not configured)'}`);
|
||||
this.logger.log(`[INIT] Using Kafka event-driven signing`);
|
||||
}
|
||||
|
||||
|
|
@ -78,38 +92,86 @@ export class MpcSigningClient implements OnModuleInit {
|
|||
}
|
||||
|
||||
/**
|
||||
* 检查热钱包是否已配置
|
||||
* 检查 C2C Bot 热钱包是否已配置
|
||||
*/
|
||||
isConfigured(): boolean {
|
||||
return !!this.hotWalletUsername && !!this.hotWalletAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取热钱包地址
|
||||
* 检查做市商钱包是否已配置
|
||||
*/
|
||||
isMarketMakerConfigured(): boolean {
|
||||
return !!this.marketMakerUsername && !!this.marketMakerAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 C2C Bot 热钱包地址
|
||||
*/
|
||||
getHotWalletAddress(): string {
|
||||
return this.hotWalletAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取热钱包用户名
|
||||
* 获取 C2C Bot 热钱包用户名
|
||||
*/
|
||||
getHotWalletUsername(): string {
|
||||
return this.hotWalletUsername;
|
||||
}
|
||||
|
||||
/**
|
||||
* 签名消息(通过 Kafka 事件驱动)
|
||||
* 获取做市商钱包地址
|
||||
*/
|
||||
getMarketMakerAddress(): string {
|
||||
return this.marketMakerAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取做市商 MPC 用户名
|
||||
*/
|
||||
getMarketMakerUsername(): string {
|
||||
return this.marketMakerUsername;
|
||||
}
|
||||
|
||||
/**
|
||||
* 签名消息(使用 C2C Bot 热钱包,通过 Kafka 事件驱动)
|
||||
*
|
||||
* @param messageHash 要签名的消息哈希 (hex string with 0x prefix)
|
||||
* @returns 签名结果 (hex string)
|
||||
*/
|
||||
async signMessage(messageHash: string): Promise<string> {
|
||||
this.logger.log(`[SIGN] Starting MPC signing for: ${messageHash.slice(0, 16)}...`);
|
||||
|
||||
if (!this.hotWalletUsername) {
|
||||
throw new Error('Hot wallet username not configured');
|
||||
}
|
||||
return this.signMessageWithUsername(this.hotWalletUsername, messageHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用做市商钱包签名消息
|
||||
*
|
||||
* @param messageHash 要签名的消息哈希 (hex string with 0x prefix)
|
||||
* @returns 签名结果 (hex string)
|
||||
*/
|
||||
async signMessageAsMarketMaker(messageHash: string): Promise<string> {
|
||||
if (!this.marketMakerUsername) {
|
||||
throw new Error('Market maker MPC username not configured');
|
||||
}
|
||||
return this.signMessageWithUsername(this.marketMakerUsername, messageHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定用户名签名消息(通过 Kafka 事件驱动)
|
||||
*
|
||||
* @param username MPC 用户名
|
||||
* @param messageHash 要签名的消息哈希 (hex string with 0x prefix)
|
||||
* @returns 签名结果 (hex string)
|
||||
*/
|
||||
async signMessageWithUsername(username: string, messageHash: string): Promise<string> {
|
||||
this.logger.log(`[SIGN] Starting MPC signing for: ${messageHash.slice(0, 16)}... (username: ${username})`);
|
||||
|
||||
if (!username) {
|
||||
throw new Error('MPC username not provided');
|
||||
}
|
||||
|
||||
const sessionId = randomUUID();
|
||||
this.logger.log(`[SIGN] Session ID: ${sessionId}`);
|
||||
|
|
@ -132,16 +194,16 @@ export class MpcSigningClient implements OnModuleInit {
|
|||
eventType: 'blockchain.mpc.signing.requested',
|
||||
toPayload: () => ({
|
||||
sessionId,
|
||||
userId: 'system', // 系统热钱包
|
||||
username: this.hotWalletUsername,
|
||||
userId: 'system',
|
||||
username,
|
||||
messageHash,
|
||||
source: 'blockchain-service',
|
||||
source: 'mining-blockchain-service',
|
||||
}),
|
||||
eventId: sessionId,
|
||||
occurredAt: new Date(),
|
||||
});
|
||||
|
||||
this.logger.log(`[SIGN] Signing request published to Kafka: sessionId=${sessionId}`);
|
||||
this.logger.log(`[SIGN] Signing request published to Kafka: sessionId=${sessionId}, username=${username}`);
|
||||
} catch (error) {
|
||||
// 发布失败,清理待处理队列
|
||||
const pending = this.pendingRequests.get(sessionId);
|
||||
|
|
|
|||
Loading…
Reference in New Issue