191 lines
6.1 KiB
TypeScript
191 lines
6.1 KiB
TypeScript
import { Controller, Post, Body, Get, Param } from '@nestjs/common';
|
||
import { ApiTags, ApiOperation, ApiResponse, ApiProperty, ApiParam } from '@nestjs/swagger';
|
||
import { IsString, IsNotEmpty, Matches, IsNumberString } from 'class-validator';
|
||
import { Erc20TransferService, TransferResult, TokenType } from '@/domain/services/erc20-transfer.service';
|
||
import { ChainTypeEnum } from '@/domain/enums';
|
||
|
||
/**
|
||
* dUSDT 转账请求 DTO
|
||
*/
|
||
class TransferDusdtDto {
|
||
@ApiProperty({ description: '接收者 Kava 地址', example: '0x1234567890abcdef1234567890abcdef12345678' })
|
||
@IsString()
|
||
@IsNotEmpty()
|
||
@Matches(/^0x[a-fA-F0-9]{40}$/, { message: 'Invalid EVM address format' })
|
||
toAddress: string;
|
||
|
||
@ApiProperty({ description: '转账金额(人类可读格式)', example: '100.5' })
|
||
@IsString()
|
||
@IsNotEmpty()
|
||
@IsNumberString({}, { message: 'Amount must be a valid number string' })
|
||
amount: string;
|
||
}
|
||
|
||
/**
|
||
* 转账结果响应 DTO
|
||
*/
|
||
class TransferResponseDto {
|
||
@ApiProperty({ description: '是否成功' })
|
||
success: boolean;
|
||
|
||
@ApiProperty({ description: '交易哈希', required: false })
|
||
txHash?: string;
|
||
|
||
@ApiProperty({ description: '错误信息', required: false })
|
||
error?: string;
|
||
|
||
@ApiProperty({ description: '消耗的 Gas', required: false })
|
||
gasUsed?: string;
|
||
|
||
@ApiProperty({ description: '区块高度', required: false })
|
||
blockNumber?: number;
|
||
}
|
||
|
||
/**
|
||
* 余额响应 DTO
|
||
*/
|
||
class BalanceResponseDto {
|
||
@ApiProperty({ description: '热钱包地址' })
|
||
address: string;
|
||
|
||
@ApiProperty({ description: 'dUSDT 余额' })
|
||
balance: string;
|
||
|
||
@ApiProperty({ description: '链类型' })
|
||
chain: string;
|
||
}
|
||
|
||
/**
|
||
* dUSDT 转账控制器
|
||
* 供 trading-service C2C Bot 调用
|
||
*/
|
||
@ApiTags('Transfer')
|
||
@Controller('transfer')
|
||
export class TransferController {
|
||
constructor(private readonly erc20TransferService: Erc20TransferService) {}
|
||
|
||
@Post('dusdt')
|
||
@ApiOperation({ summary: '转账 dUSDT 到指定地址' })
|
||
@ApiResponse({ status: 200, description: '转账结果', type: TransferResponseDto })
|
||
@ApiResponse({ status: 400, description: '参数错误' })
|
||
@ApiResponse({ status: 500, description: '转账失败' })
|
||
async transferDusdt(@Body() dto: TransferDusdtDto): Promise<TransferResponseDto> {
|
||
const result: TransferResult = await this.erc20TransferService.transferUsdt(
|
||
ChainTypeEnum.KAVA,
|
||
dto.toAddress,
|
||
dto.amount,
|
||
);
|
||
|
||
return {
|
||
success: result.success,
|
||
txHash: result.txHash,
|
||
error: result.error,
|
||
gasUsed: result.gasUsed,
|
||
blockNumber: result.blockNumber,
|
||
};
|
||
}
|
||
|
||
@Get('dusdt/balance')
|
||
@ApiOperation({ summary: '查询热钱包 dUSDT 余额' })
|
||
@ApiResponse({ status: 200, description: '余额信息', type: BalanceResponseDto })
|
||
async getHotWalletBalance(): Promise<BalanceResponseDto> {
|
||
const address = this.erc20TransferService.getHotWalletAddress(ChainTypeEnum.KAVA);
|
||
const balance = await this.erc20TransferService.getHotWalletBalance(ChainTypeEnum.KAVA);
|
||
|
||
return {
|
||
address: address || '',
|
||
balance,
|
||
chain: 'KAVA',
|
||
};
|
||
}
|
||
|
||
@Get('status')
|
||
@ApiOperation({ summary: '检查转账服务状态' })
|
||
@ApiResponse({ status: 200, description: '服务状态' })
|
||
async getStatus(): Promise<{ configured: boolean; hotWalletAddress: string | null }> {
|
||
const configured = this.erc20TransferService.isConfigured(ChainTypeEnum.KAVA);
|
||
const hotWalletAddress = this.erc20TransferService.getHotWalletAddress(ChainTypeEnum.KAVA);
|
||
|
||
return {
|
||
configured,
|
||
hotWalletAddress,
|
||
};
|
||
}
|
||
|
||
// ============ eUSDT (积分股) 转账接口 ============
|
||
|
||
@Post('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(
|
||
ChainTypeEnum.KAVA,
|
||
'EUSDT',
|
||
dto.toAddress,
|
||
dto.amount,
|
||
);
|
||
|
||
return {
|
||
success: result.success,
|
||
txHash: result.txHash,
|
||
error: result.error,
|
||
gasUsed: result.gasUsed,
|
||
blockNumber: result.blockNumber,
|
||
};
|
||
}
|
||
|
||
@Get('eusdt/balance')
|
||
@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');
|
||
|
||
return {
|
||
address: address || '',
|
||
balance,
|
||
chain: 'KAVA',
|
||
};
|
||
}
|
||
|
||
// ============ fUSDT (积分值) 转账接口 ============
|
||
|
||
@Post('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(
|
||
ChainTypeEnum.KAVA,
|
||
'FUSDT',
|
||
dto.toAddress,
|
||
dto.amount,
|
||
);
|
||
|
||
return {
|
||
success: result.success,
|
||
txHash: result.txHash,
|
||
error: result.error,
|
||
gasUsed: result.gasUsed,
|
||
blockNumber: result.blockNumber,
|
||
};
|
||
}
|
||
|
||
@Get('fusdt/balance')
|
||
@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');
|
||
|
||
return {
|
||
address: address || '',
|
||
balance,
|
||
chain: 'KAVA',
|
||
};
|
||
}
|
||
}
|