feat(mining-blockchain): 支持 eUSDT 和 fUSDT 独立做市商钱包

- 新增 EUSDT_MARKET_MAKER_USERNAME/ADDRESS 配置
- 新增 FUSDT_MARKET_MAKER_USERNAME/ADDRESS 配置
- mpc-signing.client.ts: 分离 eUSDT 和 fUSDT 做市商签名方法
- erc20-transfer.service.ts: 根据代币类型选择对应钱包转账
- transfer.controller.ts: 更新余额查询和状态接口

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-28 19:05:15 -08:00
parent aa33803d08
commit 3b95a8a332
5 changed files with 213 additions and 86 deletions

View File

@ -282,9 +282,12 @@ services:
# C2C Bot 热钱包 (MPC)
HOT_WALLET_USERNAME: ${HOT_WALLET_USERNAME:-}
HOT_WALLET_ADDRESS: ${HOT_WALLET_ADDRESS:-}
# 做市商钱包 (MPC) - 用于 eUSDT/fUSDT 转账
MARKET_MAKER_MPC_USERNAME: ${MARKET_MAKER_MPC_USERNAME:-}
MARKET_MAKER_WALLET_ADDRESS: ${MARKET_MAKER_WALLET_ADDRESS:-}
# eUSDT (积分股) 做市商钱包 (MPC)
EUSDT_MARKET_MAKER_USERNAME: ${EUSDT_MARKET_MAKER_USERNAME:-}
EUSDT_MARKET_MAKER_ADDRESS: ${EUSDT_MARKET_MAKER_ADDRESS:-}
# fUSDT (积分值) 做市商钱包 (MPC)
FUSDT_MARKET_MAKER_USERNAME: ${FUSDT_MARKET_MAKER_USERNAME:-}
FUSDT_MARKET_MAKER_ADDRESS: ${FUSDT_MARKET_MAKER_ADDRESS:-}
# 区块扫描配置
BLOCK_SCAN_INTERVAL_MS: ${BLOCK_SCAN_INTERVAL_MS:-5000}
BLOCK_CONFIRMATIONS_REQUIRED: ${BLOCK_CONFIRMATIONS_REQUIRED:-12}

View File

@ -77,13 +77,20 @@ HOT_WALLET_USERNAME=c2c-bot-wallet
HOT_WALLET_ADDRESS=
# =============================================================================
# 做市商 MPC 钱包(用于 eUSDT/fUSDT 转账)
# eUSDT (积分股) 做市商 MPC 钱包
# =============================================================================
# 做市商 MPC 用户名(用于签名转账交易)
MARKET_MAKER_MPC_USERNAME=
# MPC 用户名(用于签名转账交易)
EUSDT_MARKET_MAKER_USERNAME=
# 钱包地址EVM 地址)
EUSDT_MARKET_MAKER_ADDRESS=
# 做市商钱包地址EVM 地址)
MARKET_MAKER_WALLET_ADDRESS=
# =============================================================================
# fUSDT (积分值) 做市商 MPC 钱包
# =============================================================================
# MPC 用户名(用于签名转账交易)
FUSDT_MARKET_MAKER_USERNAME=
# 钱包地址EVM 地址)
FUSDT_MARKET_MAKER_ADDRESS=
# =============================================================================
# 区块扫描配置

View File

@ -138,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.getMarketMakerAddress(ChainTypeEnum.KAVA);
const balance = await this.erc20TransferService.getMarketMakerTokenBalance(ChainTypeEnum.KAVA, 'EUSDT');
const address = this.erc20TransferService.getEusdtMarketMakerAddress(ChainTypeEnum.KAVA);
const balance = await this.erc20TransferService.getEusdtMarketMakerTokenBalance(ChainTypeEnum.KAVA);
return {
address: address || '',
@ -177,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.getMarketMakerAddress(ChainTypeEnum.KAVA);
const balance = await this.erc20TransferService.getMarketMakerTokenBalance(ChainTypeEnum.KAVA, 'FUSDT');
const address = this.erc20TransferService.getFusdtMarketMakerAddress(ChainTypeEnum.KAVA);
const balance = await this.erc20TransferService.getFusdtMarketMakerTokenBalance(ChainTypeEnum.KAVA);
return {
address: address || '',
@ -195,13 +195,19 @@ export class TransferController {
@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);
async getMarketMakerStatus(): Promise<{
eusdt: { configured: boolean; address: string | null };
fusdt: { configured: boolean; address: string | null };
}> {
return {
configured,
marketMakerAddress,
eusdt: {
configured: this.erc20TransferService.isEusdtMarketMakerConfigured(ChainTypeEnum.KAVA),
address: this.erc20TransferService.getEusdtMarketMakerAddress(ChainTypeEnum.KAVA),
},
fusdt: {
configured: this.erc20TransferService.isFusdtMarketMakerConfigured(ChainTypeEnum.KAVA),
address: this.erc20TransferService.getFusdtMarketMakerAddress(ChainTypeEnum.KAVA),
},
};
}
}

View File

@ -38,10 +38,14 @@ export interface IMpcSigningClient {
isConfigured(): boolean;
getHotWalletAddress(): string;
signMessage(messageHash: string): Promise<string>;
// 做市商钱包
isMarketMakerConfigured(): boolean;
getMarketMakerAddress(): string;
signMessageAsMarketMaker(messageHash: string): Promise<string>;
// eUSDT (积分股) 做市商钱包
isEusdtMarketMakerConfigured(): boolean;
getEusdtMarketMakerAddress(): string;
signMessageAsEusdtMarketMaker(messageHash: string): Promise<string>;
// fUSDT (积分值) 做市商钱包
isFusdtMarketMakerConfigured(): boolean;
getFusdtMarketMakerAddress(): string;
signMessageAsFusdtMarketMaker(messageHash: string): Promise<string>;
}
export const MPC_SIGNING_CLIENT = Symbol('MPC_SIGNING_CLIENT');
@ -58,8 +62,10 @@ export class Erc20TransferService {
private readonly providers: Map<ChainTypeEnum, JsonRpcProvider> = new Map();
// C2C Bot 热钱包地址
private readonly hotWalletAddress: string;
// 做市商钱包地址
private readonly marketMakerAddress: string;
// eUSDT (积分股) 做市商钱包地址
private readonly eusdtMarketMakerAddress: string;
// fUSDT (积分值) 做市商钱包地址
private readonly fusdtMarketMakerAddress: string;
private mpcSigningClient: IMpcSigningClient | null = null;
constructor(
@ -67,7 +73,8 @@ 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.eusdtMarketMakerAddress = this.configService.get<string>('EUSDT_MARKET_MAKER_ADDRESS', '');
this.fusdtMarketMakerAddress = this.configService.get<string>('FUSDT_MARKET_MAKER_ADDRESS', '');
this.initializeProviders();
}
@ -99,11 +106,18 @@ export class Erc20TransferService {
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}`);
// 检查 eUSDT 做市商钱包地址配置
if (this.eusdtMarketMakerAddress) {
this.logger.log(`[INIT] eUSDT Market Maker address configured: ${this.eusdtMarketMakerAddress}`);
} else {
this.logger.warn('[INIT] MARKET_MAKER_WALLET_ADDRESS not configured, Market Maker transfers will fail');
this.logger.warn('[INIT] EUSDT_MARKET_MAKER_ADDRESS not configured');
}
// 检查 fUSDT 做市商钱包地址配置
if (this.fusdtMarketMakerAddress) {
this.logger.log(`[INIT] fUSDT Market Maker address configured: ${this.fusdtMarketMakerAddress}`);
} else {
this.logger.warn('[INIT] FUSDT_MARKET_MAKER_ADDRESS not configured');
}
}
@ -116,10 +130,17 @@ export class Erc20TransferService {
}
/**
*
* eUSDT
*/
getMarketMakerAddress(chainType: ChainTypeEnum): string | null {
return this.marketMakerAddress || null;
getEusdtMarketMakerAddress(chainType: ChainTypeEnum): string | null {
return this.eusdtMarketMakerAddress || null;
}
/**
* fUSDT
*/
getFusdtMarketMakerAddress(chainType: ChainTypeEnum): string | null {
return this.fusdtMarketMakerAddress || null;
}
/**
@ -585,12 +606,21 @@ export class Erc20TransferService {
}
/**
*
* eUSDT
*/
isMarketMakerConfigured(chainType: ChainTypeEnum): boolean {
isEusdtMarketMakerConfigured(chainType: ChainTypeEnum): boolean {
return this.providers.has(chainType) &&
!!this.marketMakerAddress &&
!!this.mpcSigningClient?.isMarketMakerConfigured();
!!this.eusdtMarketMakerAddress &&
!!this.mpcSigningClient?.isEusdtMarketMakerConfigured();
}
/**
* fUSDT
*/
isFusdtMarketMakerConfigured(chainType: ChainTypeEnum): boolean {
return this.providers.has(chainType) &&
!!this.fusdtMarketMakerAddress &&
!!this.mpcSigningClient?.isFusdtMarketMakerConfigured();
}
/**
@ -608,10 +638,14 @@ export class Erc20TransferService {
toAddress: string,
amount: string,
): Promise<TransferResult> {
const tokenName = tokenType === 'EUSDT' ? '积分股' : tokenType === 'FUSDT' ? '积分值' : 'dUSDT';
this.logger.log(`[MM-TRANSFER] Starting Market Maker ${tokenType} (${tokenName}) transfer`);
// 根据代币类型选择对应的做市商钱包
const isEusdt = tokenType === 'EUSDT';
const marketMakerAddress = isEusdt ? this.eusdtMarketMakerAddress : this.fusdtMarketMakerAddress;
const tokenName = isEusdt ? '积分股' : '积分值';
this.logger.log(`[MM-TRANSFER] Starting ${tokenType} (${tokenName}) Market Maker transfer`);
this.logger.log(`[MM-TRANSFER] Chain: ${chainType}`);
this.logger.log(`[MM-TRANSFER] From: ${this.marketMakerAddress}`);
this.logger.log(`[MM-TRANSFER] From: ${marketMakerAddress}`);
this.logger.log(`[MM-TRANSFER] To: ${toAddress}`);
this.logger.log(`[MM-TRANSFER] Amount: ${amount} ${tokenType}`);
@ -622,14 +656,23 @@ export class Erc20TransferService {
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 (isEusdt) {
if (!this.mpcSigningClient || !this.mpcSigningClient.isEusdtMarketMakerConfigured()) {
const error = 'eUSDT Market Maker MPC signing not configured';
this.logger.error(`[MM-TRANSFER] ${error}`);
return { success: false, error };
}
} else {
if (!this.mpcSigningClient || !this.mpcSigningClient.isFusdtMarketMakerConfigured()) {
const error = 'fUSDT 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';
if (!marketMakerAddress) {
const error = `${tokenType} Market Maker wallet address not configured`;
this.logger.error(`[MM-TRANSFER] ${error}`);
return { success: false, error };
}
@ -655,7 +698,7 @@ export class Erc20TransferService {
this.logger.log(`[MM-TRANSFER] Amount in wei: ${amountInWei.toString()}`);
// 检查余额
const balance = await contract.balanceOf(this.marketMakerAddress);
const balance = await contract.balanceOf(marketMakerAddress);
this.logger.log(`[MM-TRANSFER] Market Maker balance: ${formatUnits(balance, decimals)} ${tokenType}`);
if (balance < amountInWei) {
@ -666,7 +709,7 @@ export class Erc20TransferService {
// 构建交易
this.logger.log(`[MM-TRANSFER] Building transaction...`);
const nonce = await provider.getTransactionCount(this.marketMakerAddress);
const nonce = await provider.getTransactionCount(marketMakerAddress);
const feeData = await provider.getFeeData();
// ERC20 transfer 的 calldata
@ -674,7 +717,7 @@ export class Erc20TransferService {
// 估算 gas
const gasEstimate = await provider.estimateGas({
from: this.marketMakerAddress,
from: marketMakerAddress,
to: contractAddress,
data: transferData,
});
@ -715,9 +758,11 @@ export class Erc20TransferService {
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);
// 使用对应的做市商 MPC 钱包签名
this.logger.log(`[MM-TRANSFER] Requesting ${tokenType} Market Maker MPC signature...`);
const signatureHex = isEusdt
? await this.mpcSigningClient!.signMessageAsEusdtMarketMaker(unsignedTxHash)
: await this.mpcSigningClient!.signMessageAsFusdtMarketMaker(unsignedTxHash);
this.logger.log(`[MM-TRANSFER] MPC signature obtained: ${signatureHex.slice(0, 20)}...`);
// 解析签名
@ -733,7 +778,7 @@ export class Erc20TransferService {
const testSig = Signature.from({ r, s, yParity });
const recoveredAddress = recoverAddress(unsignedTxHash, testSig);
if (recoveredAddress.toLowerCase() === this.marketMakerAddress.toLowerCase()) {
if (recoveredAddress.toLowerCase() === marketMakerAddress.toLowerCase()) {
this.logger.log(`[MM-TRANSFER] Found correct yParity: ${yParity}`);
signature = testSig;
break;
@ -786,25 +831,50 @@ export class Erc20TransferService {
}
/**
*
* eUSDT
*/
async getMarketMakerTokenBalance(chainType: ChainTypeEnum, tokenType: TokenType): Promise<string> {
async getEusdtMarketMakerTokenBalance(chainType: ChainTypeEnum): 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');
if (!this.eusdtMarketMakerAddress) {
throw new Error('eUSDT Market Maker wallet address not configured');
}
const contractAddress = this.getTokenContract(chainType, tokenType);
const contractAddress = this.getTokenContract(chainType, 'EUSDT');
if (!contractAddress) {
throw new Error(`Token ${tokenType} not configured for chain ${chainType}`);
throw new Error(`eUSDT not configured for chain ${chainType}`);
}
const contract = new Contract(contractAddress, ERC20_TRANSFER_ABI, provider);
const balance = await contract.balanceOf(this.marketMakerAddress);
const balance = await contract.balanceOf(this.eusdtMarketMakerAddress);
const decimals = await contract.decimals();
return formatUnits(balance, decimals);
}
/**
* fUSDT
*/
async getFusdtMarketMakerTokenBalance(chainType: ChainTypeEnum): Promise<string> {
const provider = this.providers.get(chainType);
if (!provider) {
throw new Error(`Provider not configured for chain: ${chainType}`);
}
if (!this.fusdtMarketMakerAddress) {
throw new Error('fUSDT Market Maker wallet address not configured');
}
const contractAddress = this.getTokenContract(chainType, 'FUSDT');
if (!contractAddress) {
throw new Error(`fUSDT not configured for chain ${chainType}`);
}
const contract = new Contract(contractAddress, ERC20_TRANSFER_ABI, provider);
const balance = await contract.balanceOf(this.fusdtMarketMakerAddress);
const decimals = await contract.decimals();
return formatUnits(balance, decimals);

View File

@ -42,9 +42,12 @@ export class MpcSigningClient implements OnModuleInit {
// C2C Bot 热钱包
private readonly hotWalletUsername: string;
private readonly hotWalletAddress: string;
// 做市商 MPC 钱包
private readonly marketMakerUsername: string;
private readonly marketMakerAddress: string;
// eUSDT (积分股) 做市商钱包
private readonly eusdtMarketMakerUsername: string;
private readonly eusdtMarketMakerAddress: string;
// fUSDT (积分值) 做市商钱包
private readonly fusdtMarketMakerUsername: string;
private readonly fusdtMarketMakerAddress: string;
private readonly signingTimeoutMs: number = 300000; // 5 minutes
// 待处理的签名请求回调 Map<sessionId, { resolve, reject, timeout }>
@ -62,9 +65,12 @@ export class MpcSigningClient implements OnModuleInit {
// 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', '');
// eUSDT (积分股) 做市商钱包配置
this.eusdtMarketMakerUsername = this.configService.get<string>('EUSDT_MARKET_MAKER_USERNAME', '');
this.eusdtMarketMakerAddress = this.configService.get<string>('EUSDT_MARKET_MAKER_ADDRESS', '');
// fUSDT (积分值) 做市商钱包配置
this.fusdtMarketMakerUsername = this.configService.get<string>('FUSDT_MARKET_MAKER_USERNAME', '');
this.fusdtMarketMakerAddress = this.configService.get<string>('FUSDT_MARKET_MAKER_ADDRESS', '');
if (!this.hotWalletUsername) {
this.logger.warn('[INIT] HOT_WALLET_USERNAME not configured (C2C Bot disabled)');
@ -72,15 +78,16 @@ export class MpcSigningClient implements OnModuleInit {
if (!this.hotWalletAddress) {
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.eusdtMarketMakerUsername || !this.eusdtMarketMakerAddress) {
this.logger.warn('[INIT] eUSDT Market Maker not configured');
}
if (!this.marketMakerAddress) {
this.logger.warn('[INIT] MARKET_MAKER_WALLET_ADDRESS not configured (Market Maker disabled)');
if (!this.fusdtMarketMakerUsername || !this.fusdtMarketMakerAddress) {
this.logger.warn('[INIT] fUSDT Market Maker 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] eUSDT Market Maker: ${this.eusdtMarketMakerAddress || '(not configured)'}`);
this.logger.log(`[INIT] fUSDT Market Maker: ${this.fusdtMarketMakerAddress || '(not configured)'}`);
this.logger.log(`[INIT] Using Kafka event-driven signing`);
}
@ -99,10 +106,17 @@ export class MpcSigningClient implements OnModuleInit {
}
/**
*
* eUSDT
*/
isMarketMakerConfigured(): boolean {
return !!this.marketMakerUsername && !!this.marketMakerAddress;
isEusdtMarketMakerConfigured(): boolean {
return !!this.eusdtMarketMakerUsername && !!this.eusdtMarketMakerAddress;
}
/**
* fUSDT
*/
isFusdtMarketMakerConfigured(): boolean {
return !!this.fusdtMarketMakerUsername && !!this.fusdtMarketMakerAddress;
}
/**
@ -120,17 +134,31 @@ export class MpcSigningClient implements OnModuleInit {
}
/**
*
* eUSDT
*/
getMarketMakerAddress(): string {
return this.marketMakerAddress;
getEusdtMarketMakerAddress(): string {
return this.eusdtMarketMakerAddress;
}
/**
* MPC
* eUSDT MPC
*/
getMarketMakerUsername(): string {
return this.marketMakerUsername;
getEusdtMarketMakerUsername(): string {
return this.eusdtMarketMakerUsername;
}
/**
* fUSDT
*/
getFusdtMarketMakerAddress(): string {
return this.fusdtMarketMakerAddress;
}
/**
* fUSDT MPC
*/
getFusdtMarketMakerUsername(): string {
return this.fusdtMarketMakerUsername;
}
/**
@ -147,16 +175,29 @@ export class MpcSigningClient implements OnModuleInit {
}
/**
* 使
* 使 eUSDT
*
* @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');
async signMessageAsEusdtMarketMaker(messageHash: string): Promise<string> {
if (!this.eusdtMarketMakerUsername) {
throw new Error('eUSDT Market Maker MPC username not configured');
}
return this.signMessageWithUsername(this.marketMakerUsername, messageHash);
return this.signMessageWithUsername(this.eusdtMarketMakerUsername, messageHash);
}
/**
* 使 fUSDT
*
* @param messageHash (hex string with 0x prefix)
* @returns (hex string)
*/
async signMessageAsFusdtMarketMaker(messageHash: string): Promise<string> {
if (!this.fusdtMarketMakerUsername) {
throw new Error('fUSDT Market Maker MPC username not configured');
}
return this.signMessageWithUsername(this.fusdtMarketMakerUsername, messageHash);
}
/**