feat(mpc-service): add blockchain-service client for address derivation

- Add BlockchainClientService to call blockchain-service /internal/derive-address
- Call derive addresses after keygen completes with MPC public key
- Include derived addresses (BSC, KAVA, DST) in keygen completed event

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-06 23:27:30 -08:00
parent cf308efecf
commit 0ab1bf0dcc
4 changed files with 124 additions and 0 deletions

View File

@ -14,6 +14,7 @@ import {
KeygenRequestedPayload,
} from '../../infrastructure/messaging/kafka/event-consumer.service';
import { BackupClientService } from '../../infrastructure/external/backup';
import { BlockchainClientService } from '../../infrastructure/external/blockchain';
import { KeygenStartedEvent } from '../../domain/events/keygen-started.event';
import { KeygenCompletedEvent } from '../../domain/events/keygen-completed.event';
import { SessionFailedEvent } from '../../domain/events/session-failed.event';
@ -28,6 +29,7 @@ export class KeygenRequestedHandler implements OnModuleInit {
private readonly eventPublisher: EventPublisherService,
private readonly mpcCoordinator: MPCCoordinatorService,
private readonly backupClient: BackupClientService,
private readonly blockchainClient: BlockchainClientService,
) {}
async onModuleInit() {
@ -74,6 +76,20 @@ export class KeygenRequestedHandler implements OnModuleInit {
// Cache public key
await this.mpcCoordinator.savePublicKeyCache(username, result.publicKey);
// Step 3: Derive addresses for all chains (BSC, KAVA, DST) via blockchain-service
let derivedAddresses: { chainType: string; address: string }[] = [];
try {
const deriveResult = await this.blockchainClient.deriveAddresses({
userId,
publicKey: result.publicKey,
});
derivedAddresses = deriveResult.addresses;
this.logger.log(`Addresses derived: userId=${userId}, count=${derivedAddresses.length}`);
} catch (deriveError) {
// 地址派生失败不阻塞主流程,但记录错误
this.logger.error(`Failed to derive addresses: userId=${userId}`, deriveError);
}
// Save delegate share to backup-service if exists
if (result.delegateShare) {
// 1. 保存到本地缓存(用于签名时快速访问)
@ -115,6 +131,7 @@ export class KeygenRequestedHandler implements OnModuleInit {
userId,
username,
delegateShare: result.delegateShare,
derivedAddresses, // BSC, KAVA, DST addresses
serverParties: [], // mpc-system manages this
};

View File

@ -0,0 +1,102 @@
/**
* Blockchain Client Service
*
* mpc-service blockchain-service
*/
import { Injectable, Logger } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { ConfigService } from '@nestjs/config';
import { firstValueFrom } from 'rxjs';
export interface DeriveAddressParams {
userId: string;
publicKey: string;
}
export interface DerivedAddress {
chainType: string;
address: string;
}
export interface DeriveAddressResult {
userId: string;
publicKey: string;
addresses: DerivedAddress[];
}
@Injectable()
export class BlockchainClientService {
private readonly logger = new Logger(BlockchainClientService.name);
private readonly blockchainServiceUrl: string;
constructor(
private readonly httpService: HttpService,
private readonly configService: ConfigService,
) {
this.blockchainServiceUrl = this.configService.get<string>(
'BLOCKCHAIN_SERVICE_URL',
'http://blockchain-service:3000',
);
this.logger.log(`[INIT] BlockchainClientService initialized`);
this.logger.log(`[INIT] URL: ${this.blockchainServiceUrl}`);
}
/**
* (BSC, KAVA, DST)
*/
async deriveAddresses(params: DeriveAddressParams): Promise<DeriveAddressResult> {
this.logger.log(`Deriving addresses: userId=${params.userId}, publicKey=${params.publicKey.substring(0, 20)}...`);
try {
const response = await firstValueFrom(
this.httpService.post<DeriveAddressResult>(
`${this.blockchainServiceUrl}/internal/derive-address`,
{
userId: params.userId,
publicKey: params.publicKey,
},
{
headers: {
'Content-Type': 'application/json',
},
timeout: 30000,
},
),
);
this.logger.log(`Addresses derived successfully: userId=${params.userId}, count=${response.data.addresses.length}`);
for (const addr of response.data.addresses) {
this.logger.log(` ${addr.chainType}: ${addr.address}`);
}
return response.data;
} catch (error) {
this.logger.error(`Failed to derive addresses: userId=${params.userId}`, error);
throw error;
}
}
/**
*
*/
async getUserAddresses(userId: string): Promise<DerivedAddress[]> {
this.logger.log(`Getting user addresses: userId=${userId}`);
try {
const response = await firstValueFrom(
this.httpService.get<{ userId: string; addresses: DerivedAddress[] }>(
`${this.blockchainServiceUrl}/internal/user/${userId}/addresses`,
{
timeout: 30000,
},
),
);
return response.data.addresses;
} catch (error) {
this.logger.error(`Failed to get user addresses: userId=${userId}`, error);
throw error;
}
}
}

View File

@ -0,0 +1 @@
export * from './blockchain-client.service';

View File

@ -5,6 +5,7 @@
* - PrismaService delegate share
* - Kafka
* - BackupClientService delegate share backup-service
* - BlockchainClientService
*/
import { Global, Module } from '@nestjs/common';
@ -20,6 +21,7 @@ import { EventConsumerService } from './messaging/kafka/event-consumer.service';
// External Services
import { BackupClientService } from './external/backup';
import { BlockchainClientService } from './external/blockchain';
@Global()
@Module({
@ -40,12 +42,14 @@ import { BackupClientService } from './external/backup';
// External Services
BackupClientService,
BlockchainClientService,
],
exports: [
PrismaService,
EventPublisherService,
EventConsumerService,
BackupClientService,
BlockchainClientService,
],
})
export class InfrastructureModule {}