feat(market-maker): 积分股(eUSDT)和积分值(fUSDT)使用独立 MPC 钱包

- Prisma: MarketMakerConfig 新增 eusdt_wallet_address 字段
- 后端: 初始化/更新配置时读取 EUSDT_MARKET_MAKER_ADDRESS 环境变量
- 后端: getConfig API 返回 eusdtWalletAddress
- 前端: 积分股充值弹窗使用 eusdtWalletAddress (不再共用 kavaWalletAddress)
- 前端: 积分值充值弹窗继续使用 kavaWalletAddress (fUSDT 钱包)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-02-03 03:39:54 -08:00
parent 38ee808239
commit ebe7123583
7 changed files with 43 additions and 19 deletions

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "MarketMakerConfig" ADD COLUMN "eusdt_wallet_address" VARCHAR(42);

View File

@ -471,8 +471,10 @@ model MarketMakerConfig {
accountSequence String @unique @map("account_sequence") // 做市商专用交易账户 accountSequence String @unique @map("account_sequence") // 做市商专用交易账户
// ============ 区块链钱包配置MPC============ // ============ 区块链钱包配置MPC============
// 做市商 Kava 链钱包地址MPC 钱包,用于充值监控和提现) // fUSDT (积分值) 做市商 Kava 链钱包地址MPC 钱包,用于充值监控和提现)
kavaWalletAddress String? @map("kava_wallet_address") @db.VarChar(42) kavaWalletAddress String? @map("kava_wallet_address") @db.VarChar(42)
// eUSDT (积分股) 做市商 Kava 链钱包地址MPC 钱包,用于积分股充值监控和提现)
eusdtWalletAddress String? @map("eusdt_wallet_address") @db.VarChar(42)
// MPC 用户名(用于签名提现交易) // MPC 用户名(用于签名提现交易)
mpcUsername String? @map("mpc_username") @db.VarChar(100) mpcUsername String? @map("mpc_username") @db.VarChar(100)

View File

@ -82,6 +82,10 @@ class UpdateConfigDto {
@IsOptional() @IsOptional()
@IsString() @IsString()
kavaWalletAddress?: string; kavaWalletAddress?: string;
@IsOptional()
@IsString()
eusdtWalletAddress?: string;
} }
class UpdateMakerConfigDto { class UpdateMakerConfigDto {
@ -214,6 +218,7 @@ export class MarketMakerController {
discountRate: config.discountRate.toString(), discountRate: config.discountRate.toString(),
isActive: config.isActive, isActive: config.isActive,
kavaWalletAddress: config.kavaWalletAddress, kavaWalletAddress: config.kavaWalletAddress,
eusdtWalletAddress: config.eusdtWalletAddress,
}, },
runningStatus, runningStatus,
}; };

View File

@ -38,6 +38,7 @@ export interface MarketMakerConfig {
discountRate: Decimal; discountRate: Decimal;
isActive: boolean; isActive: boolean;
kavaWalletAddress: string | null; kavaWalletAddress: string | null;
eusdtWalletAddress: string | null;
} }
export enum LedgerType { export enum LedgerType {
@ -106,6 +107,7 @@ export class MarketMakerService {
discountRate: new Decimal(config.discountRate.toString()), discountRate: new Decimal(config.discountRate.toString()),
isActive: config.isActive, isActive: config.isActive,
kavaWalletAddress: config.kavaWalletAddress, kavaWalletAddress: config.kavaWalletAddress,
eusdtWalletAddress: config.eusdtWalletAddress,
}; };
} }
@ -153,16 +155,22 @@ export class MarketMakerService {
const existing = await this.getConfig(name); const existing = await this.getConfig(name);
if (existing) { if (existing) {
// 如果存在但钱包地址为空,尝试从环境变量更新 // 如果存在但钱包地址为空,尝试从环境变量更新
const updateData: any = {};
if (!existing.kavaWalletAddress) { if (!existing.kavaWalletAddress) {
const kavaWalletAddress = this.configService.get<string>('FUSDT_MARKET_MAKER_ADDRESS'); const kavaWalletAddress = this.configService.get<string>('FUSDT_MARKET_MAKER_ADDRESS');
if (kavaWalletAddress) { if (kavaWalletAddress) updateData.kavaWalletAddress = kavaWalletAddress;
await this.prisma.marketMakerConfig.update({ }
where: { name }, if (!existing.eusdtWalletAddress) {
data: { kavaWalletAddress }, const eusdtWalletAddress = this.configService.get<string>('EUSDT_MARKET_MAKER_ADDRESS');
}); if (eusdtWalletAddress) updateData.eusdtWalletAddress = eusdtWalletAddress;
this.logger.log(`Market maker ${name} wallet address updated from env: ${kavaWalletAddress}`); }
return this.getConfig(name) as Promise<MarketMakerConfig>; if (Object.keys(updateData).length > 0) {
} await this.prisma.marketMakerConfig.update({
where: { name },
data: updateData,
});
this.logger.log(`Market maker ${name} wallet addresses updated from env: ${JSON.stringify(updateData)}`);
return this.getConfig(name) as Promise<MarketMakerConfig>;
} }
return existing; return existing;
} }
@ -176,6 +184,7 @@ export class MarketMakerService {
// 从环境变量读取做市商钱包地址 // 从环境变量读取做市商钱包地址
const kavaWalletAddress = this.configService.get<string>('FUSDT_MARKET_MAKER_ADDRESS') || null; const kavaWalletAddress = this.configService.get<string>('FUSDT_MARKET_MAKER_ADDRESS') || null;
const eusdtWalletAddress = this.configService.get<string>('EUSDT_MARKET_MAKER_ADDRESS') || null;
// 创建做市商配置 // 创建做市商配置
const config = await this.prisma.marketMakerConfig.create({ const config = await this.prisma.marketMakerConfig.create({
@ -187,10 +196,11 @@ export class MarketMakerService {
minIntervalMs: params.minIntervalMs || 1000, minIntervalMs: params.minIntervalMs || 1000,
maxIntervalMs: params.maxIntervalMs || 1000, maxIntervalMs: params.maxIntervalMs || 1000,
kavaWalletAddress, kavaWalletAddress,
eusdtWalletAddress,
}, },
}); });
this.logger.log(`Market maker initialized: ${name}, account: ${params.accountSequence}, wallet: ${kavaWalletAddress || 'not configured'}`); this.logger.log(`Market maker initialized: ${name}, account: ${params.accountSequence}, fUSDT wallet: ${kavaWalletAddress || 'not configured'}, eUSDT wallet: ${eusdtWalletAddress || 'not configured'}`);
return this.getConfig(name) as Promise<MarketMakerConfig>; return this.getConfig(name) as Promise<MarketMakerConfig>;
} }
@ -761,6 +771,7 @@ export class MarketMakerService {
priceStrategy?: string; priceStrategy?: string;
discountRate?: number; discountRate?: number;
kavaWalletAddress?: string; kavaWalletAddress?: string;
eusdtWalletAddress?: string;
}, },
): Promise<MarketMakerConfig | null> { ): Promise<MarketMakerConfig | null> {
const config = await this.getConfig(name); const config = await this.getConfig(name);

View File

@ -447,21 +447,21 @@ export default function MarketMakerPage() {
<div className="text-sm text-muted-foreground text-center"> <div className="text-sm text-muted-foreground text-center">
<strong>eUSDT</strong> () <strong>eUSDT</strong> ()
</div> </div>
{config.kavaWalletAddress ? ( {config.eusdtWalletAddress ? (
<div className="flex flex-col items-center space-y-4"> <div className="flex flex-col items-center space-y-4">
<div className="p-4 bg-white rounded-lg"> <div className="p-4 bg-white rounded-lg">
<QRCodeSVG value={config.kavaWalletAddress} size={180} /> <QRCodeSVG value={config.eusdtWalletAddress} size={180} />
</div> </div>
<div className="w-full"> <div className="w-full">
<Label className="text-xs text-muted-foreground"> (Kava EVM)</Label> <Label className="text-xs text-muted-foreground">eUSDT (Kava EVM)</Label>
<div className="flex items-center gap-2 mt-1"> <div className="flex items-center gap-2 mt-1">
<code className="flex-1 text-xs bg-muted p-2 rounded break-all"> <code className="flex-1 text-xs bg-muted p-2 rounded break-all">
{config.kavaWalletAddress} {config.eusdtWalletAddress}
</code> </code>
<Button <Button
size="sm" size="sm"
variant="outline" variant="outline"
onClick={() => handleCopyAddress(config.kavaWalletAddress!)} onClick={() => handleCopyAddress(config.eusdtWalletAddress!)}
> >
{copiedAddress ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />} {copiedAddress ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
</Button> </Button>
@ -476,10 +476,11 @@ export default function MarketMakerPage() {
<div className="space-y-4"> <div className="space-y-4">
<div className="text-center text-muted-foreground py-2"> <div className="text-center text-muted-foreground py-2">
<AlertCircle className="h-6 w-6 mx-auto mb-2 text-yellow-500" /> <AlertCircle className="h-6 w-6 mx-auto mb-2 text-yellow-500" />
<p className="text-sm"></p> <p className="text-sm">eUSDT </p>
<p className="text-xs mt-1"> .env EUSDT_MARKET_MAKER_ADDRESS</p>
</div> </div>
<div> <div>
<Label className="text-xs"> (Kava EVM)</Label> <Label className="text-xs"> eUSDT (Kava EVM)</Label>
<Input <Input
value={walletAddressInput} value={walletAddressInput}
onChange={(e) => setWalletAddressInput(e.target.value)} onChange={(e) => setWalletAddressInput(e.target.value)}
@ -490,7 +491,7 @@ export default function MarketMakerPage() {
<Button <Button
className="w-full" className="w-full"
onClick={() => { onClick={() => {
updateConfigMutation.mutate({ kavaWalletAddress: walletAddressInput }); updateConfigMutation.mutate({ eusdtWalletAddress: walletAddressInput });
setWalletAddressInput(''); setWalletAddressInput('');
}} }}
disabled={updateConfigMutation.isPending || !walletAddressInput || !walletAddressInput.startsWith('0x')} disabled={updateConfigMutation.isPending || !walletAddressInput || !walletAddressInput.startsWith('0x')}

View File

@ -79,6 +79,7 @@ export interface MarketMakerConfig {
refreshIntervalMs?: number; refreshIntervalMs?: number;
lastRefreshAt?: string; lastRefreshAt?: string;
kavaWalletAddress?: string; kavaWalletAddress?: string;
eusdtWalletAddress?: string;
} }
export interface MarketMakerOrder { export interface MarketMakerOrder {
@ -143,6 +144,7 @@ export const marketMakerApi = {
priceStrategy?: string; priceStrategy?: string;
discountRate?: number; discountRate?: number;
kavaWalletAddress?: string; kavaWalletAddress?: string;
eusdtWalletAddress?: string;
}): Promise<{ success: boolean; message: string }> => { }): Promise<{ success: boolean; message: string }> => {
const response = await tradingClient.post(`/admin/market-maker/${name}/config`, data); const response = await tradingClient.post(`/admin/market-maker/${name}/config`, data);
return response.data; return response.data;

View File

@ -40,6 +40,7 @@ export function useUpdateMarketMakerConfig() {
priceStrategy?: string; priceStrategy?: string;
discountRate?: number; discountRate?: number;
kavaWalletAddress?: string; kavaWalletAddress?: string;
eusdtWalletAddress?: string;
}) => marketMakerApi.updateConfig(MARKET_MAKER_NAME, data), }) => marketMakerApi.updateConfig(MARKET_MAKER_NAME, data),
onSuccess: (data) => { onSuccess: (data) => {
toast({ title: '成功', description: data.message }); toast({ title: '成功', description: data.message });