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") // 做市商专用交易账户
// ============ 区块链钱包配置MPC============
// 做市商 Kava 链钱包地址MPC 钱包,用于充值监控和提现)
kavaWalletAddress String? @map("kava_wallet_address") @db.VarChar(42)
// fUSDT (积分值) 做市商 Kava 链钱包地址MPC 钱包,用于充值监控和提现)
kavaWalletAddress String? @map("kava_wallet_address") @db.VarChar(42)
// eUSDT (积分股) 做市商 Kava 链钱包地址MPC 钱包,用于积分股充值监控和提现)
eusdtWalletAddress String? @map("eusdt_wallet_address") @db.VarChar(42)
// MPC 用户名(用于签名提现交易)
mpcUsername String? @map("mpc_username") @db.VarChar(100)

View File

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

View File

@ -38,6 +38,7 @@ export interface MarketMakerConfig {
discountRate: Decimal;
isActive: boolean;
kavaWalletAddress: string | null;
eusdtWalletAddress: string | null;
}
export enum LedgerType {
@ -106,6 +107,7 @@ export class MarketMakerService {
discountRate: new Decimal(config.discountRate.toString()),
isActive: config.isActive,
kavaWalletAddress: config.kavaWalletAddress,
eusdtWalletAddress: config.eusdtWalletAddress,
};
}
@ -153,16 +155,22 @@ export class MarketMakerService {
const existing = await this.getConfig(name);
if (existing) {
// 如果存在但钱包地址为空,尝试从环境变量更新
const updateData: any = {};
if (!existing.kavaWalletAddress) {
const kavaWalletAddress = this.configService.get<string>('FUSDT_MARKET_MAKER_ADDRESS');
if (kavaWalletAddress) {
await this.prisma.marketMakerConfig.update({
where: { name },
data: { kavaWalletAddress },
});
this.logger.log(`Market maker ${name} wallet address updated from env: ${kavaWalletAddress}`);
return this.getConfig(name) as Promise<MarketMakerConfig>;
}
if (kavaWalletAddress) updateData.kavaWalletAddress = kavaWalletAddress;
}
if (!existing.eusdtWalletAddress) {
const eusdtWalletAddress = this.configService.get<string>('EUSDT_MARKET_MAKER_ADDRESS');
if (eusdtWalletAddress) updateData.eusdtWalletAddress = eusdtWalletAddress;
}
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;
}
@ -176,6 +184,7 @@ export class MarketMakerService {
// 从环境变量读取做市商钱包地址
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({
@ -187,10 +196,11 @@ export class MarketMakerService {
minIntervalMs: params.minIntervalMs || 1000,
maxIntervalMs: params.maxIntervalMs || 1000,
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>;
}
@ -761,6 +771,7 @@ export class MarketMakerService {
priceStrategy?: string;
discountRate?: number;
kavaWalletAddress?: string;
eusdtWalletAddress?: string;
},
): Promise<MarketMakerConfig | null> {
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">
<strong>eUSDT</strong> ()
</div>
{config.kavaWalletAddress ? (
{config.eusdtWalletAddress ? (
<div className="flex flex-col items-center space-y-4">
<div className="p-4 bg-white rounded-lg">
<QRCodeSVG value={config.kavaWalletAddress} size={180} />
<QRCodeSVG value={config.eusdtWalletAddress} size={180} />
</div>
<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">
<code className="flex-1 text-xs bg-muted p-2 rounded break-all">
{config.kavaWalletAddress}
{config.eusdtWalletAddress}
</code>
<Button
size="sm"
variant="outline"
onClick={() => handleCopyAddress(config.kavaWalletAddress!)}
onClick={() => handleCopyAddress(config.eusdtWalletAddress!)}
>
{copiedAddress ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
</Button>
@ -476,10 +476,11 @@ export default function MarketMakerPage() {
<div className="space-y-4">
<div className="text-center text-muted-foreground py-2">
<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>
<Label className="text-xs"> (Kava EVM)</Label>
<Label className="text-xs"> eUSDT (Kava EVM)</Label>
<Input
value={walletAddressInput}
onChange={(e) => setWalletAddressInput(e.target.value)}
@ -490,7 +491,7 @@ export default function MarketMakerPage() {
<Button
className="w-full"
onClick={() => {
updateConfigMutation.mutate({ kavaWalletAddress: walletAddressInput });
updateConfigMutation.mutate({ eusdtWalletAddress: walletAddressInput });
setWalletAddressInput('');
}}
disabled={updateConfigMutation.isPending || !walletAddressInput || !walletAddressInput.startsWith('0x')}

View File

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

View File

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