From 0ab1bf0dccd1fea9844d24421d811c73fc2428f6 Mon Sep 17 00:00:00 2001 From: hailin Date: Sat, 6 Dec 2025 23:27:30 -0800 Subject: [PATCH] feat(mpc-service): add blockchain-service client for address derivation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../keygen-requested.handler.ts | 17 +++ .../blockchain/blockchain-client.service.ts | 102 ++++++++++++++++++ .../external/blockchain/index.ts | 1 + .../infrastructure/infrastructure.module.ts | 4 + 4 files changed, 124 insertions(+) create mode 100644 backend/services/mpc-service/src/infrastructure/external/blockchain/blockchain-client.service.ts create mode 100644 backend/services/mpc-service/src/infrastructure/external/blockchain/index.ts diff --git a/backend/services/mpc-service/src/application/event-handlers/keygen-requested.handler.ts b/backend/services/mpc-service/src/application/event-handlers/keygen-requested.handler.ts index aa57ddca..ee1a6b37 100644 --- a/backend/services/mpc-service/src/application/event-handlers/keygen-requested.handler.ts +++ b/backend/services/mpc-service/src/application/event-handlers/keygen-requested.handler.ts @@ -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 }; diff --git a/backend/services/mpc-service/src/infrastructure/external/blockchain/blockchain-client.service.ts b/backend/services/mpc-service/src/infrastructure/external/blockchain/blockchain-client.service.ts new file mode 100644 index 00000000..b3044310 --- /dev/null +++ b/backend/services/mpc-service/src/infrastructure/external/blockchain/blockchain-client.service.ts @@ -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( + '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 { + this.logger.log(`Deriving addresses: userId=${params.userId}, publicKey=${params.publicKey.substring(0, 20)}...`); + + try { + const response = await firstValueFrom( + this.httpService.post( + `${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 { + 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; + } + } +} diff --git a/backend/services/mpc-service/src/infrastructure/external/blockchain/index.ts b/backend/services/mpc-service/src/infrastructure/external/blockchain/index.ts new file mode 100644 index 00000000..854b8857 --- /dev/null +++ b/backend/services/mpc-service/src/infrastructure/external/blockchain/index.ts @@ -0,0 +1 @@ +export * from './blockchain-client.service'; diff --git a/backend/services/mpc-service/src/infrastructure/infrastructure.module.ts b/backend/services/mpc-service/src/infrastructure/infrastructure.module.ts index 350fe3ab..967ec469 100644 --- a/backend/services/mpc-service/src/infrastructure/infrastructure.module.ts +++ b/backend/services/mpc-service/src/infrastructure/infrastructure.module.ts @@ -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 {}