From 852073ae1105e215c8a6a0da54327f42d63a5ad3 Mon Sep 17 00:00:00 2001 From: hailin Date: Sun, 7 Dec 2025 00:11:06 -0800 Subject: [PATCH] refactor: move mnemonic verification from identity-service to blockchain-service MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add /internal/verify-mnemonic API to blockchain-service - Add /internal/derive-from-mnemonic API to blockchain-service - Create MnemonicDerivationAdapter for BIP39 mnemonic address derivation - Create BlockchainClientService in identity-service to call blockchain-service - Remove WalletGeneratorService from identity-service - Update recover-by-mnemonic handler to use blockchain-service API This enforces proper domain boundaries - all blockchain/crypto operations are now handled by blockchain-service. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../api/controllers/internal.controller.ts | 33 +- .../src/api/dto/request/index.ts | 1 + .../api/dto/request/verify-mnemonic.dto.ts | 22 ++ .../src/infrastructure/blockchain/index.ts | 1 + .../blockchain/mnemonic-derivation.adapter.ts | 148 +++++++++ .../infrastructure/infrastructure.module.ts | 4 +- .../.claude/settings.local.json | 5 +- .../recover-by-mnemonic.handler.ts | 37 ++- .../services/user-application.service.ts | 30 +- .../src/domain/domain.module.ts | 2 - .../src/domain/services/index.ts | 8 - .../services/wallet-generator.service.ts | 87 ----- .../blockchain/blockchain-client.service.ts | 104 ++++++ .../external/blockchain/blockchain.module.ts | 8 - .../wallet-generator.service.impl.ts | 79 ----- .../wallet-generator.service.spec.ts | 296 ------------------ .../blockchain/wallet-generator.service.ts | 200 ------------ .../infrastructure/infrastructure.module.ts | 16 +- 18 files changed, 366 insertions(+), 715 deletions(-) create mode 100644 backend/services/blockchain-service/src/api/dto/request/verify-mnemonic.dto.ts create mode 100644 backend/services/blockchain-service/src/infrastructure/blockchain/mnemonic-derivation.adapter.ts delete mode 100644 backend/services/identity-service/src/domain/services/wallet-generator.service.ts create mode 100644 backend/services/identity-service/src/infrastructure/external/blockchain/blockchain-client.service.ts delete mode 100644 backend/services/identity-service/src/infrastructure/external/blockchain/blockchain.module.ts delete mode 100644 backend/services/identity-service/src/infrastructure/external/blockchain/wallet-generator.service.impl.ts delete mode 100644 backend/services/identity-service/src/infrastructure/external/blockchain/wallet-generator.service.spec.ts delete mode 100644 backend/services/identity-service/src/infrastructure/external/blockchain/wallet-generator.service.ts diff --git a/backend/services/blockchain-service/src/api/controllers/internal.controller.ts b/backend/services/blockchain-service/src/api/controllers/internal.controller.ts index f2d2bb2d..df0f3482 100644 --- a/backend/services/blockchain-service/src/api/controllers/internal.controller.ts +++ b/backend/services/blockchain-service/src/api/controllers/internal.controller.ts @@ -1,7 +1,8 @@ import { Controller, Post, Body, Get, Param } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; import { AddressDerivationService } from '@/application/services/address-derivation.service'; -import { DeriveAddressDto } from '../dto/request'; +import { MnemonicDerivationAdapter } from '@/infrastructure/blockchain'; +import { DeriveAddressDto, VerifyMnemonicDto } from '../dto/request'; import { DeriveAddressResponseDto } from '../dto/response'; /** @@ -11,7 +12,10 @@ import { DeriveAddressResponseDto } from '../dto/response'; @ApiTags('Internal') @Controller('internal') export class InternalController { - constructor(private readonly addressDerivationService: AddressDerivationService) {} + constructor( + private readonly addressDerivationService: AddressDerivationService, + private readonly mnemonicDerivation: MnemonicDerivationAdapter, + ) {} @Post('derive-address') @ApiOperation({ summary: '从公钥派生地址' }) @@ -45,4 +49,29 @@ export class InternalController { })), }; } + + @Post('verify-mnemonic') + @ApiOperation({ summary: '验证助记词是否匹配指定地址' }) + @ApiResponse({ status: 200, description: '验证结果' }) + async verifyMnemonic(@Body() dto: VerifyMnemonicDto) { + const result = this.mnemonicDerivation.verifyMnemonic(dto.mnemonic, dto.expectedAddresses); + return { + valid: result.valid, + matchedAddresses: result.matchedAddresses, + mismatchedAddresses: result.mismatchedAddresses, + }; + } + + @Post('derive-from-mnemonic') + @ApiOperation({ summary: '从助记词派生所有链地址' }) + @ApiResponse({ status: 200, description: '派生的地址列表' }) + async deriveFromMnemonic(@Body() dto: { mnemonic: string }) { + const addresses = this.mnemonicDerivation.deriveAllAddresses(dto.mnemonic); + return { + addresses: addresses.map((a) => ({ + chainType: a.chainType, + address: a.address, + })), + }; + } } diff --git a/backend/services/blockchain-service/src/api/dto/request/index.ts b/backend/services/blockchain-service/src/api/dto/request/index.ts index 41844d57..7ef0e722 100644 --- a/backend/services/blockchain-service/src/api/dto/request/index.ts +++ b/backend/services/blockchain-service/src/api/dto/request/index.ts @@ -1,2 +1,3 @@ export * from './query-balance.dto'; export * from './derive-address.dto'; +export * from './verify-mnemonic.dto'; diff --git a/backend/services/blockchain-service/src/api/dto/request/verify-mnemonic.dto.ts b/backend/services/blockchain-service/src/api/dto/request/verify-mnemonic.dto.ts new file mode 100644 index 00000000..112291db --- /dev/null +++ b/backend/services/blockchain-service/src/api/dto/request/verify-mnemonic.dto.ts @@ -0,0 +1,22 @@ +import { IsString, IsArray, ArrayMinSize } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; + +export class VerifyMnemonicDto { + @ApiProperty({ + description: '助记词 (12或24个单词,空格分隔)', + example: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about', + }) + @IsString() + mnemonic: string; + + @ApiProperty({ + description: '期望的钱包地址列表,用于验证助记词', + example: [{ chainType: 'KAVA', address: 'kava1abc...' }], + }) + @IsArray() + @ArrayMinSize(1) + expectedAddresses: Array<{ + chainType: string; + address: string; + }>; +} diff --git a/backend/services/blockchain-service/src/infrastructure/blockchain/index.ts b/backend/services/blockchain-service/src/infrastructure/blockchain/index.ts index 2eb1198b..97de5db9 100644 --- a/backend/services/blockchain-service/src/infrastructure/blockchain/index.ts +++ b/backend/services/blockchain-service/src/infrastructure/blockchain/index.ts @@ -1,3 +1,4 @@ export * from './evm-provider.adapter'; export * from './address-derivation.adapter'; +export * from './mnemonic-derivation.adapter'; export * from './block-scanner.service'; diff --git a/backend/services/blockchain-service/src/infrastructure/blockchain/mnemonic-derivation.adapter.ts b/backend/services/blockchain-service/src/infrastructure/blockchain/mnemonic-derivation.adapter.ts new file mode 100644 index 00000000..20462a79 --- /dev/null +++ b/backend/services/blockchain-service/src/infrastructure/blockchain/mnemonic-derivation.adapter.ts @@ -0,0 +1,148 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { HDKey } from '@scure/bip32'; +import { mnemonicToSeedSync, validateMnemonic } from '@scure/bip39'; +import { wordlist } from '@scure/bip39/wordlists/english'; +import { createHash } from 'crypto'; +import { bech32 } from 'bech32'; +import { Wallet } from 'ethers'; +import { ChainTypeEnum } from '@/domain/enums'; + +export interface MnemonicDerivedAddress { + chainType: ChainTypeEnum; + address: string; +} + +/** + * 链配置 + */ +const CHAIN_CONFIG: Record = { + [ChainTypeEnum.KAVA]: { + derivationPath: "m/44'/459'/0'/0/0", + prefix: 'kava', + }, + [ChainTypeEnum.DST]: { + derivationPath: "m/44'/118'/0'/0/0", + prefix: 'dst', + }, + [ChainTypeEnum.BSC]: { + derivationPath: "m/44'/60'/0'/0/0", + }, +}; + +/** + * 助记词派生适配器 + * 从 BIP39 助记词派生多链钱包地址 + */ +@Injectable() +export class MnemonicDerivationAdapter { + private readonly logger = new Logger(MnemonicDerivationAdapter.name); + + /** + * 验证助记词格式 + */ + validateMnemonic(mnemonic: string): boolean { + return validateMnemonic(mnemonic, wordlist); + } + + /** + * 从助记词派生所有支持链的地址 + */ + deriveAllAddresses(mnemonic: string): MnemonicDerivedAddress[] { + if (!this.validateMnemonic(mnemonic)) { + throw new Error('Invalid mnemonic'); + } + + const seed = mnemonicToSeedSync(mnemonic); + const addresses: MnemonicDerivedAddress[] = []; + + this.logger.log('[DERIVE] Starting mnemonic address derivation'); + + // KAVA + const kavaAddress = this.deriveCosmosAddress(seed, CHAIN_CONFIG[ChainTypeEnum.KAVA]); + addresses.push({ chainType: ChainTypeEnum.KAVA, address: kavaAddress }); + this.logger.log(`[DERIVE] KAVA address: ${kavaAddress}`); + + // DST + const dstAddress = this.deriveCosmosAddress(seed, CHAIN_CONFIG[ChainTypeEnum.DST]); + addresses.push({ chainType: ChainTypeEnum.DST, address: dstAddress }); + this.logger.log(`[DERIVE] DST address: ${dstAddress}`); + + // BSC + const bscAddress = this.deriveEvmAddress(seed, CHAIN_CONFIG[ChainTypeEnum.BSC]); + addresses.push({ chainType: ChainTypeEnum.BSC, address: bscAddress }); + this.logger.log(`[DERIVE] BSC address: ${bscAddress}`); + + return addresses; + } + + /** + * 验证助记词是否对应指定的地址 + */ + verifyMnemonic( + mnemonic: string, + expectedAddresses: Array<{ chainType: string; address: string }>, + ): { valid: boolean; matchedAddresses: string[]; mismatchedAddresses: string[] } { + if (!this.validateMnemonic(mnemonic)) { + return { valid: false, matchedAddresses: [], mismatchedAddresses: [] }; + } + + const derivedAddresses = this.deriveAllAddresses(mnemonic); + const derivedMap = new Map(derivedAddresses.map((a) => [a.chainType, a.address.toLowerCase()])); + + const matchedAddresses: string[] = []; + const mismatchedAddresses: string[] = []; + + for (const expected of expectedAddresses) { + const chainType = expected.chainType as ChainTypeEnum; + const derivedAddress = derivedMap.get(chainType); + + if (derivedAddress && derivedAddress === expected.address.toLowerCase()) { + matchedAddresses.push(expected.chainType); + } else { + mismatchedAddresses.push(expected.chainType); + } + } + + return { + valid: mismatchedAddresses.length === 0 && matchedAddresses.length > 0, + matchedAddresses, + mismatchedAddresses, + }; + } + + /** + * 派生 Cosmos 地址 (bech32 格式) + */ + private deriveCosmosAddress( + seed: Uint8Array, + config: { derivationPath: string; prefix?: string }, + ): string { + const hdkey = HDKey.fromMasterSeed(seed); + const childKey = hdkey.derive(config.derivationPath); + + if (!childKey.publicKey) { + throw new Error('Failed to derive public key'); + } + + const hash = createHash('sha256').update(childKey.publicKey).digest(); + const addressHash = createHash('ripemd160').update(hash).digest(); + const words = bech32.toWords(addressHash); + + return bech32.encode(config.prefix!, words); + } + + /** + * 派生 EVM 地址 + */ + private deriveEvmAddress(seed: Uint8Array, config: { derivationPath: string }): string { + const hdkey = HDKey.fromMasterSeed(seed); + const childKey = hdkey.derive(config.derivationPath); + + if (!childKey.privateKey) { + throw new Error('Failed to derive private key'); + } + + const wallet = new Wallet(Buffer.from(childKey.privateKey).toString('hex')); + return wallet.address; + } +} diff --git a/backend/services/blockchain-service/src/infrastructure/infrastructure.module.ts b/backend/services/blockchain-service/src/infrastructure/infrastructure.module.ts index 04c632d3..adc8cb30 100644 --- a/backend/services/blockchain-service/src/infrastructure/infrastructure.module.ts +++ b/backend/services/blockchain-service/src/infrastructure/infrastructure.module.ts @@ -2,7 +2,7 @@ import { Global, Module } from '@nestjs/common'; import { PrismaService } from './persistence/prisma/prisma.service'; import { RedisService, AddressCacheService } from './redis'; import { EventPublisherService, MpcEventConsumerService } from './kafka'; -import { EvmProviderAdapter, AddressDerivationAdapter, BlockScannerService } from './blockchain'; +import { EvmProviderAdapter, AddressDerivationAdapter, MnemonicDerivationAdapter, BlockScannerService } from './blockchain'; import { DomainModule } from '@/domain/domain.module'; import { DEPOSIT_TRANSACTION_REPOSITORY, @@ -30,6 +30,7 @@ import { // 区块链适配器 EvmProviderAdapter, AddressDerivationAdapter, + MnemonicDerivationAdapter, BlockScannerService, // 缓存服务 @@ -60,6 +61,7 @@ import { MpcEventConsumerService, EvmProviderAdapter, AddressDerivationAdapter, + MnemonicDerivationAdapter, BlockScannerService, AddressCacheService, DEPOSIT_TRANSACTION_REPOSITORY, diff --git a/backend/services/identity-service/.claude/settings.local.json b/backend/services/identity-service/.claude/settings.local.json index 6a56fe55..f485dfed 100644 --- a/backend/services/identity-service/.claude/settings.local.json +++ b/backend/services/identity-service/.claude/settings.local.json @@ -30,7 +30,10 @@ "Bash(powershell -Command:*)", "Bash(git pull:*)", "Bash(git -C \"c:\\Users\\dong\\Desktop\\rwadurian\" add -A)", - "Bash(git -C \"c:\\Users\\dong\\Desktop\\rwadurian\" status)" + "Bash(git -C \"c:\\Users\\dong\\Desktop\\rwadurian\" status)", + "Bash(git -C \"c:\\Users\\dong\\Desktop\\rwadurian\" commit -m \"$(cat <<''EOF''\nfix(mpc-service): change healthcheck from wget to curl\n\nDocker compose healthcheck was using wget which is not installed in the\nnode:20-slim image. Changed to use curl and corrected endpoint path.\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude Opus 4.5 \nEOF\n)\")", + "Bash(git -C \"c:\\Users\\dong\\Desktop\\rwadurian\" push)", + "Bash(git -C \"c:\\Users\\dong\\Desktop\\rwadurian\" show cf308ef --stat)" ], "deny": [], "ask": [] diff --git a/backend/services/identity-service/src/application/commands/recover-by-mnemonic/recover-by-mnemonic.handler.ts b/backend/services/identity-service/src/application/commands/recover-by-mnemonic/recover-by-mnemonic.handler.ts index e64a725a..0887bee8 100644 --- a/backend/services/identity-service/src/application/commands/recover-by-mnemonic/recover-by-mnemonic.handler.ts +++ b/backend/services/identity-service/src/application/commands/recover-by-mnemonic/recover-by-mnemonic.handler.ts @@ -1,19 +1,21 @@ -import { Injectable, Inject } from '@nestjs/common'; +import { Injectable, Inject, Logger } from '@nestjs/common'; import { RecoverByMnemonicCommand } from './recover-by-mnemonic.command'; import { UserAccountRepository, USER_ACCOUNT_REPOSITORY } from '@/domain/repositories/user-account.repository.interface'; -import { WalletGeneratorService } from '@/domain/services'; -import { AccountSequence, ChainType, Mnemonic } from '@/domain/value-objects'; +import { AccountSequence, ChainType } from '@/domain/value-objects'; import { TokenService } from '@/application/services/token.service'; import { EventPublisherService } from '@/infrastructure/kafka/event-publisher.service'; +import { BlockchainClientService } from '@/infrastructure/external/blockchain/blockchain-client.service'; import { ApplicationError } from '@/shared/exceptions/domain.exception'; import { RecoverAccountResult } from '../index'; @Injectable() export class RecoverByMnemonicHandler { + private readonly logger = new Logger(RecoverByMnemonicHandler.name); + constructor( @Inject(USER_ACCOUNT_REPOSITORY) private readonly userRepository: UserAccountRepository, - private readonly walletGenerator: WalletGeneratorService, + private readonly blockchainClient: BlockchainClientService, private readonly tokenService: TokenService, private readonly eventPublisher: EventPublisherService, ) {} @@ -24,18 +26,31 @@ export class RecoverByMnemonicHandler { if (!account) throw new ApplicationError('账户序列号不存在'); if (!account.isActive) throw new ApplicationError('账户已冻结或注销'); - const mnemonic = Mnemonic.create(command.mnemonic); - const wallets = this.walletGenerator.recoverWalletSystem({ - userId: account.userId, - mnemonic, - deviceId: command.newDeviceId, + // 获取账户的钱包地址用于验证 + const expectedAddresses: Array<{ chainType: string; address: string }> = []; + const kavaWallet = account.getWalletAddress(ChainType.KAVA); + if (kavaWallet) { + expectedAddresses.push({ chainType: 'KAVA', address: kavaWallet.address }); + } + + if (expectedAddresses.length === 0) { + throw new ApplicationError('账户没有关联的钱包地址'); + } + + // 调用 blockchain-service 验证助记词 + this.logger.log(`Verifying mnemonic for account ${command.accountSequence}`); + const verifyResult = await this.blockchainClient.verifyMnemonic({ + mnemonic: command.mnemonic, + expectedAddresses, }); - const kavaWallet = account.getWalletAddress(ChainType.KAVA); - if (!kavaWallet || kavaWallet.address !== wallets.get(ChainType.KAVA)!.address) { + if (!verifyResult.valid) { + this.logger.warn(`Mnemonic verification failed for account ${command.accountSequence}`); throw new ApplicationError('助记词错误'); } + this.logger.log(`Mnemonic verified successfully for account ${command.accountSequence}`); + account.addDevice(command.newDeviceId, command.deviceName); account.recordLogin(); await this.userRepository.save(account); diff --git a/backend/services/identity-service/src/application/services/user-application.service.ts b/backend/services/identity-service/src/application/services/user-application.service.ts index b47aaedd..f66945cf 100644 --- a/backend/services/identity-service/src/application/services/user-application.service.ts +++ b/backend/services/identity-service/src/application/services/user-application.service.ts @@ -4,16 +4,17 @@ import { MpcKeyShareRepository, MPC_KEY_SHARE_REPOSITORY } from '@/domain/reposi import { UserAccount } from '@/domain/aggregates/user-account/user-account.aggregate'; import { WalletAddress } from '@/domain/entities/wallet-address.entity'; import { - AccountSequenceGeneratorService, UserValidatorService, WalletGeneratorService, + AccountSequenceGeneratorService, UserValidatorService, } from '@/domain/services'; import { UserId, PhoneNumber, ReferralCode, AccountSequence, ProvinceCode, CityCode, - ChainType, Mnemonic, KYCInfo, HardwareInfo, + ChainType, KYCInfo, HardwareInfo, } from '@/domain/value-objects'; import { TokenService } from './token.service'; import { RedisService } from '@/infrastructure/redis/redis.service'; import { SmsService } from '@/infrastructure/external/sms/sms.service'; import { EventPublisherService } from '@/infrastructure/kafka/event-publisher.service'; +import { BlockchainClientService } from '@/infrastructure/external/blockchain/blockchain-client.service'; import { MpcWalletService } from '@/infrastructure/external/mpc'; import { ApplicationError } from '@/shared/exceptions/domain.exception'; import { generateRandomIdentity } from '@/shared/utils'; @@ -40,7 +41,7 @@ export class UserApplicationService { private readonly mpcKeyShareRepository: MpcKeyShareRepository, private readonly sequenceGenerator: AccountSequenceGeneratorService, private readonly validatorService: UserValidatorService, - private readonly walletGenerator: WalletGeneratorService, + private readonly blockchainClient: BlockchainClientService, private readonly mpcWalletService: MpcWalletService, private readonly tokenService: TokenService, private readonly redisService: RedisService, @@ -155,15 +156,24 @@ export class UserApplicationService { if (!account) throw new ApplicationError('账户序列号不存在'); if (!account.isActive) throw new ApplicationError('账户已冻结或注销'); - const mnemonic = Mnemonic.create(command.mnemonic); - const wallets = this.walletGenerator.recoverWalletSystem({ - userId: account.userId, - mnemonic, - deviceId: command.newDeviceId, + // 获取账户的钱包地址用于验证 + const expectedAddresses: Array<{ chainType: string; address: string }> = []; + const kavaWallet = account.getWalletAddress(ChainType.KAVA); + if (kavaWallet) { + expectedAddresses.push({ chainType: 'KAVA', address: kavaWallet.address }); + } + + if (expectedAddresses.length === 0) { + throw new ApplicationError('账户没有关联的钱包地址'); + } + + // 调用 blockchain-service 验证助记词 + const verifyResult = await this.blockchainClient.verifyMnemonic({ + mnemonic: command.mnemonic, + expectedAddresses, }); - const kavaWallet = account.getWalletAddress(ChainType.KAVA); - if (!kavaWallet || kavaWallet.address !== wallets.get(ChainType.KAVA)!.address) { + if (!verifyResult.valid) { throw new ApplicationError('助记词错误'); } diff --git a/backend/services/identity-service/src/domain/domain.module.ts b/backend/services/identity-service/src/domain/domain.module.ts index 032fe74e..b97da4ef 100644 --- a/backend/services/identity-service/src/domain/domain.module.ts +++ b/backend/services/identity-service/src/domain/domain.module.ts @@ -11,14 +11,12 @@ import { InfrastructureModule } from '@/infrastructure/infrastructure.module'; { provide: USER_ACCOUNT_REPOSITORY, useClass: UserAccountRepositoryImpl }, AccountSequenceGeneratorService, UserValidatorService, - // WalletGeneratorService 由 InfrastructureModule 提供 UserAccountFactory, ], exports: [ USER_ACCOUNT_REPOSITORY, AccountSequenceGeneratorService, UserValidatorService, - // WalletGeneratorService 由 InfrastructureModule 导出 UserAccountFactory, ], }) diff --git a/backend/services/identity-service/src/domain/services/index.ts b/backend/services/identity-service/src/domain/services/index.ts index 4318b9b3..7e72252a 100644 --- a/backend/services/identity-service/src/domain/services/index.ts +++ b/backend/services/identity-service/src/domain/services/index.ts @@ -2,14 +2,6 @@ import { Injectable, Inject } from '@nestjs/common'; import { UserAccountRepository, USER_ACCOUNT_REPOSITORY } from '@/domain/repositories/user-account.repository.interface'; import { AccountSequence, PhoneNumber, ReferralCode, ChainType } from '@/domain/value-objects'; -// 导出 WalletGeneratorService 和相关类型 -export { - WalletGeneratorService, - MpcWalletGenerationParams, - MpcWalletGenerationResult, - ChainWalletInfo, -} from './wallet-generator.service'; - // ============ ValidationResult ============ export class ValidationResult { private constructor( diff --git a/backend/services/identity-service/src/domain/services/wallet-generator.service.ts b/backend/services/identity-service/src/domain/services/wallet-generator.service.ts deleted file mode 100644 index 2003c1e9..00000000 --- a/backend/services/identity-service/src/domain/services/wallet-generator.service.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { WalletAddress, MpcSignature } from '@/domain/entities/wallet-address.entity'; -import { ChainType, Mnemonic, UserId } from '@/domain/value-objects'; - -/** - * MPC 钱包生成参数 - */ -export interface MpcWalletGenerationParams { - userId: string; - username: string; // 用户名 (用于 MPC keygen) - deviceId: string; -} - -/** - * 链钱包信息 - */ -export interface ChainWalletInfo { - chainType: 'KAVA' | 'DST' | 'BSC'; - address: string; - publicKey: string; - addressDigest: string; - signature: MpcSignature; // 64 bytes hex (R + S) -} - -/** - * MPC 钱包生成结果 - */ -export interface MpcWalletGenerationResult { - publicKey: string; - delegateShare: string; // delegate share (加密的用户分片) - serverParties: string[]; // 服务器 party IDs - wallets: ChainWalletInfo[]; - sessionId: string; -} - -/** - * 钱包生成服务接口 (端口) - * - * 定义钱包生成的业务接口,由基础设施层实现 - */ -export abstract class WalletGeneratorService { - /** - * 使用 MPC 2-of-3 生成三链钱包 - * - * @param params 用户ID和设备ID - * @returns MPC 钱包生成结果,包含分片和签名信息 - */ - abstract generateMpcWalletSystem(params: MpcWalletGenerationParams): Promise; - - /** - * 将 MPC 钱包信息转换为领域实体 - * - * @param userId 用户ID - * @param walletInfos MPC 钱包信息数组 - * @returns 钱包地址实体 Map - */ - convertToWalletEntities( - userId: UserId, - walletInfos: ChainWalletInfo[], - ): Map { - const wallets = new Map(); - - for (const info of walletInfos) { - const chainType = ChainType[info.chainType as keyof typeof ChainType]; - const wallet = WalletAddress.createMpc({ - userId, - chainType, - address: info.address, - publicKey: info.publicKey, - addressDigest: info.addressDigest, - signature: info.signature, - }); - wallets.set(chainType, wallet); - } - - return wallets; - } - - /** - * @deprecated MPC 模式下不再使用助记词恢复 - * 此方法保留用于向后兼容旧版账户恢复流程 - */ - abstract recoverWalletSystem(params: { - userId: UserId; - mnemonic: Mnemonic; - deviceId: string; - }): Map; -} diff --git a/backend/services/identity-service/src/infrastructure/external/blockchain/blockchain-client.service.ts b/backend/services/identity-service/src/infrastructure/external/blockchain/blockchain-client.service.ts new file mode 100644 index 00000000..e16493a2 --- /dev/null +++ b/backend/services/identity-service/src/infrastructure/external/blockchain/blockchain-client.service.ts @@ -0,0 +1,104 @@ +/** + * Blockchain Client Service + * + * identity-service 调用 blockchain-service API + * - 验证助记词 + * - 从助记词派生地址 + */ + +import { Injectable, Logger } from '@nestjs/common'; +import { HttpService } from '@nestjs/axios'; +import { ConfigService } from '@nestjs/config'; +import { firstValueFrom } from 'rxjs'; + +export interface VerifyMnemonicParams { + mnemonic: string; + expectedAddresses: Array<{ + chainType: string; + address: string; + }>; +} + +export interface VerifyMnemonicResult { + valid: boolean; + matchedAddresses: string[]; + mismatchedAddresses: string[]; +} + +export interface DerivedAddress { + chainType: string; + address: string; +} + +@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}`); + } + + /** + * 验证助记词是否匹配指定的钱包地址 + */ + async verifyMnemonic(params: VerifyMnemonicParams): Promise { + this.logger.log(`Verifying mnemonic against ${params.expectedAddresses.length} addresses`); + + try { + const response = await firstValueFrom( + this.httpService.post( + `${this.blockchainServiceUrl}/internal/verify-mnemonic`, + { + mnemonic: params.mnemonic, + expectedAddresses: params.expectedAddresses, + }, + { + headers: { 'Content-Type': 'application/json' }, + timeout: 30000, + }, + ), + ); + + this.logger.log(`Mnemonic verification result: valid=${response.data.valid}`); + return response.data; + } catch (error) { + this.logger.error('Failed to verify mnemonic', error); + throw error; + } + } + + /** + * 从助记词派生所有链的钱包地址 + */ + async deriveFromMnemonic(mnemonic: string): Promise { + this.logger.log('Deriving addresses from mnemonic'); + + try { + const response = await firstValueFrom( + this.httpService.post<{ addresses: DerivedAddress[] }>( + `${this.blockchainServiceUrl}/internal/derive-from-mnemonic`, + { mnemonic }, + { + headers: { 'Content-Type': 'application/json' }, + timeout: 30000, + }, + ), + ); + + this.logger.log(`Derived ${response.data.addresses.length} addresses from mnemonic`); + return response.data.addresses; + } catch (error) { + this.logger.error('Failed to derive addresses from mnemonic', error); + throw error; + } + } +} diff --git a/backend/services/identity-service/src/infrastructure/external/blockchain/blockchain.module.ts b/backend/services/identity-service/src/infrastructure/external/blockchain/blockchain.module.ts deleted file mode 100644 index 2dc96ba0..00000000 --- a/backend/services/identity-service/src/infrastructure/external/blockchain/blockchain.module.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Module } from '@nestjs/common'; -import { WalletGeneratorServiceImpl } from './wallet-generator.service.impl'; - -@Module({ - providers: [WalletGeneratorServiceImpl], - exports: [WalletGeneratorServiceImpl], -}) -export class BlockchainModule {} diff --git a/backend/services/identity-service/src/infrastructure/external/blockchain/wallet-generator.service.impl.ts b/backend/services/identity-service/src/infrastructure/external/blockchain/wallet-generator.service.impl.ts deleted file mode 100644 index 3bdd4328..00000000 --- a/backend/services/identity-service/src/infrastructure/external/blockchain/wallet-generator.service.impl.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { createHash } from 'crypto'; -import { - WalletGeneratorService, - MpcWalletGenerationParams, - MpcWalletGenerationResult, -} from '@/domain/services/wallet-generator.service'; -import { WalletAddress } from '@/domain/entities/wallet-address.entity'; -import { ChainType, Mnemonic, UserId } from '@/domain/value-objects'; -import { MpcWalletService } from '@/infrastructure/external/mpc/mpc-wallet.service'; - -/** - * 钱包生成服务实现 - * - * 使用 MPC 2-of-3 协议生成三链钱包地址并签名 - */ -@Injectable() -export class WalletGeneratorServiceImpl extends WalletGeneratorService { - private readonly logger = new Logger(WalletGeneratorServiceImpl.name); - - constructor(private readonly mpcWalletService: MpcWalletService) { - super(); - } - - /** - * 使用 MPC 2-of-3 生成三链钱包 - */ - async generateMpcWalletSystem( - params: MpcWalletGenerationParams, - ): Promise { - this.logger.log(`Generating MPC wallet system for user=${params.userId}`); - - const result = await this.mpcWalletService.generateMpcWallet(params); - - this.logger.log( - `MPC wallet system generated: ${result.wallets.length} wallets, sessionId=${result.sessionId}`, - ); - - return result; - } - - /** - * @deprecated MPC 模式下不再使用助记词恢复 - * 此方法保留用于向后兼容旧版账户恢复流程 - */ - recoverWalletSystem(params: { - userId: UserId; - mnemonic: Mnemonic; - deviceId: string; - }): Map { - this.logger.warn( - 'recoverWalletSystem is deprecated - MPC mode does not use mnemonic recovery', - ); - - const encryptionKey = this.deriveEncryptionKey( - params.deviceId, - params.userId.toString(), - ); - const wallets = new Map(); - const chains = [ChainType.KAVA, ChainType.DST, ChainType.BSC]; - - for (const chainType of chains) { - const wallet = WalletAddress.createFromMnemonic({ - userId: params.userId, - chainType, - mnemonic: params.mnemonic, - encryptionKey, - }); - wallets.set(chainType, wallet); - } - - return wallets; - } - - private deriveEncryptionKey(deviceId: string, userId: string): string { - const input = `${deviceId}:${userId}`; - return createHash('sha256').update(input).digest('hex'); - } -} diff --git a/backend/services/identity-service/src/infrastructure/external/blockchain/wallet-generator.service.spec.ts b/backend/services/identity-service/src/infrastructure/external/blockchain/wallet-generator.service.spec.ts deleted file mode 100644 index 64206893..00000000 --- a/backend/services/identity-service/src/infrastructure/external/blockchain/wallet-generator.service.spec.ts +++ /dev/null @@ -1,296 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { ConfigService } from '@nestjs/config'; -import { WalletGeneratorService } from './wallet-generator.service'; -import { ChainType, Mnemonic } from '@/domain/value-objects'; - -describe('WalletGeneratorService', () => { - let service: WalletGeneratorService; - let configService: ConfigService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - WalletGeneratorService, - { - provide: ConfigService, - useValue: { - get: jest.fn().mockReturnValue('test-salt'), - }, - }, - ], - }).compile(); - - service = module.get(WalletGeneratorService); - configService = module.get(ConfigService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); - - describe('generateWalletSystem', () => { - it('应该生成完整的钱包系统', () => { - const result = service.generateWalletSystem({ - userId: '12345', - deviceId: 'test-device-id', - }); - - expect(result).toBeDefined(); - expect(result.mnemonic).toBeDefined(); - expect(result.mnemonic).toBeInstanceOf(Mnemonic); - expect(result.wallets).toBeDefined(); - expect(result.wallets.size).toBe(3); - }); - - it('应该为所有链生成钱包地址', () => { - const result = service.generateWalletSystem({ - userId: '12345', - deviceId: 'test-device-id', - }); - - expect(result.wallets.has(ChainType.KAVA)).toBe(true); - expect(result.wallets.has(ChainType.DST)).toBe(true); - expect(result.wallets.has(ChainType.BSC)).toBe(true); - }); - - it('生成的 KAVA 地址应该有正确的格式', () => { - const result = service.generateWalletSystem({ - userId: '12345', - deviceId: 'test-device-id', - }); - - const kavaWallet = result.wallets.get(ChainType.KAVA); - expect(kavaWallet).toBeDefined(); - expect(kavaWallet!.address).toMatch(/^kava1[a-z0-9]{38}$/); - }); - - it('生成的 DST 地址应该有正确的格式', () => { - const result = service.generateWalletSystem({ - userId: '12345', - deviceId: 'test-device-id', - }); - - const dstWallet = result.wallets.get(ChainType.DST); - expect(dstWallet).toBeDefined(); - expect(dstWallet!.address).toMatch(/^dst1[a-z0-9]{38}$/); - }); - - it('生成的 BSC 地址应该有正确的格式', () => { - const result = service.generateWalletSystem({ - userId: '12345', - deviceId: 'test-device-id', - }); - - const bscWallet = result.wallets.get(ChainType.BSC); - expect(bscWallet).toBeDefined(); - expect(bscWallet!.address).toMatch(/^0x[a-fA-F0-9]{40}$/); - }); - - it('每次生成的钱包应该不同', () => { - const result1 = service.generateWalletSystem({ - userId: '11111', - deviceId: 'test-device-id-1', - }); - - const result2 = service.generateWalletSystem({ - userId: '22222', - deviceId: 'test-device-id-2', - }); - - expect(result1.mnemonic.value).not.toBe(result2.mnemonic.value); - expect(result1.wallets.get(ChainType.KAVA)!.address).not.toBe( - result2.wallets.get(ChainType.KAVA)!.address - ); - }); - }); - - describe('recoverWalletSystem', () => { - it('应该使用助记词恢复钱包系统', () => { - // 先生成一个钱包系统 - const original = service.generateWalletSystem({ - userId: '12345', - deviceId: 'test-device-id', - }); - - // 使用助记词恢复 - const recovered = service.recoverWalletSystem({ - userId: '12345', - mnemonic: original.mnemonic, - deviceId: 'new-device-id', - }); - - expect(recovered.size).toBe(3); - - // 验证恢复的地址与原地址相同 - expect(recovered.get(ChainType.KAVA)!.address).toBe( - original.wallets.get(ChainType.KAVA)!.address - ); - expect(recovered.get(ChainType.DST)!.address).toBe( - original.wallets.get(ChainType.DST)!.address - ); - expect(recovered.get(ChainType.BSC)!.address).toBe( - original.wallets.get(ChainType.BSC)!.address - ); - }); - - it('不同设备应该生成相同的地址(相同助记词)', () => { - const original = service.generateWalletSystem({ - userId: '12345', - deviceId: 'device-1', - }); - - const recovered1 = service.recoverWalletSystem({ - userId: '12345', - mnemonic: original.mnemonic, - deviceId: 'device-2', - }); - - const recovered2 = service.recoverWalletSystem({ - userId: '12345', - mnemonic: original.mnemonic, - deviceId: 'device-3', - }); - - expect(recovered1.get(ChainType.KAVA)!.address).toBe( - recovered2.get(ChainType.KAVA)!.address - ); - }); - }); - - describe('deriveAddress', () => { - it('相同的助记词应该派生相同的地址', () => { - const mnemonic = Mnemonic.generate(); - - const address1 = service.deriveAddress(ChainType.KAVA, mnemonic); - const address2 = service.deriveAddress(ChainType.KAVA, mnemonic); - - expect(address1).toBe(address2); - }); - - it('不同的助记词应该派生不同的地址', () => { - const mnemonic1 = Mnemonic.generate(); - const mnemonic2 = Mnemonic.generate(); - - const address1 = service.deriveAddress(ChainType.KAVA, mnemonic1); - const address2 = service.deriveAddress(ChainType.KAVA, mnemonic2); - - expect(address1).not.toBe(address2); - }); - - it('应该为不同的链派生不同的地址', () => { - const mnemonic = Mnemonic.generate(); - - const kavaAddress = service.deriveAddress(ChainType.KAVA, mnemonic); - const dstAddress = service.deriveAddress(ChainType.DST, mnemonic); - const bscAddress = service.deriveAddress(ChainType.BSC, mnemonic); - - expect(kavaAddress).not.toBe(dstAddress); - expect(kavaAddress).not.toBe(bscAddress); - expect(dstAddress).not.toBe(bscAddress); - }); - }); - - describe('verifyMnemonic', () => { - it('应该验证正确的助记词', () => { - const result = service.generateWalletSystem({ - userId: '12345', - deviceId: 'test-device-id', - }); - - const kavaWallet = result.wallets.get(ChainType.KAVA)!; - const isValid = service.verifyMnemonic( - result.mnemonic, - ChainType.KAVA, - kavaWallet.address - ); - - expect(isValid).toBe(true); - }); - - it('应该拒绝错误的助记词', () => { - const result1 = service.generateWalletSystem({ - userId: '11111', - deviceId: 'test-device-id-1', - }); - - const result2 = service.generateWalletSystem({ - userId: '22222', - deviceId: 'test-device-id-2', - }); - - const kavaWallet1 = result1.wallets.get(ChainType.KAVA)!; - const isValid = service.verifyMnemonic( - result2.mnemonic, - ChainType.KAVA, - kavaWallet1.address - ); - - expect(isValid).toBe(false); - }); - }); - - describe('加密和解密', () => { - it('应该正确加密和解密助记词', () => { - const mnemonic = 'test mnemonic phrase for encryption'; - const key = 'encryption-key'; - - const encrypted = service.encryptMnemonic(mnemonic, key); - expect(encrypted).toBeDefined(); - expect(encrypted.encrypted).toBeDefined(); - expect(encrypted.iv).toBeDefined(); - expect(encrypted.authTag).toBeDefined(); - - const decrypted = service.decryptMnemonic(encrypted, key); - expect(decrypted).toBe(mnemonic); - }); - - it('使用错误的密钥应该解密失败', () => { - const mnemonic = 'test mnemonic phrase'; - const key = 'correct-key'; - const wrongKey = 'wrong-key'; - - const encrypted = service.encryptMnemonic(mnemonic, key); - - expect(() => { - service.decryptMnemonic(encrypted, wrongKey); - }).toThrow(); - }); - - it('相同的助记词和密钥应该生成不同的密文(因为随机 IV)', () => { - const mnemonic = 'test mnemonic phrase'; - const key = 'encryption-key'; - - const encrypted1 = service.encryptMnemonic(mnemonic, key); - const encrypted2 = service.encryptMnemonic(mnemonic, key); - - // 密文应该不同(因为 IV 不同) - expect(encrypted1.encrypted).not.toBe(encrypted2.encrypted); - expect(encrypted1.iv).not.toBe(encrypted2.iv); - - // 但解密后应该相同 - const decrypted1 = service.decryptMnemonic(encrypted1, key); - const decrypted2 = service.decryptMnemonic(encrypted2, key); - expect(decrypted1).toBe(mnemonic); - expect(decrypted2).toBe(mnemonic); - }); - }); - - describe('deriveEncryptionKey', () => { - it('相同的输入应该生成相同的密钥', () => { - const key1 = service.deriveEncryptionKey('device-1', 'user-1'); - const key2 = service.deriveEncryptionKey('device-1', 'user-1'); - - expect(key1).toBe(key2); - }); - - it('不同的输入应该生成不同的密钥', () => { - const key1 = service.deriveEncryptionKey('device-1', 'user-1'); - const key2 = service.deriveEncryptionKey('device-2', 'user-1'); - const key3 = service.deriveEncryptionKey('device-1', 'user-2'); - - expect(key1).not.toBe(key2); - expect(key1).not.toBe(key3); - expect(key2).not.toBe(key3); - }); - }); -}); diff --git a/backend/services/identity-service/src/infrastructure/external/blockchain/wallet-generator.service.ts b/backend/services/identity-service/src/infrastructure/external/blockchain/wallet-generator.service.ts deleted file mode 100644 index 35ac946e..00000000 --- a/backend/services/identity-service/src/infrastructure/external/blockchain/wallet-generator.service.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { HDKey } from '@scure/bip32'; -import { - createHash, - createCipheriv, - createDecipheriv, - randomBytes, - scryptSync, -} from 'crypto'; -import { bech32 } from 'bech32'; -import { ethers } from 'ethers'; -import { ConfigService } from '@nestjs/config'; -import { Mnemonic, UserId, ChainType, CHAIN_CONFIG } from '@/domain/value-objects'; -import { WalletAddress } from '@/domain/entities/wallet-address.entity'; -import { DomainError } from '@/shared/exceptions/domain.exception'; - -export interface WalletSystemResult { - mnemonic: Mnemonic; - wallets: Map; -} - -export interface EncryptedMnemonicData { - encrypted: string; - authTag: string; - iv: string; -} - -@Injectable() -export class WalletGeneratorService { - private readonly logger = new Logger(WalletGeneratorService.name); - private readonly encryptionSalt: string; - - constructor(private readonly configService: ConfigService) { - this.encryptionSalt = configService.get( - 'WALLET_ENCRYPTION_SALT', - 'rwa-wallet-salt', - ); - } - - generateWalletSystem(params: { - userId: string; - deviceId: string; - }): WalletSystemResult { - const mnemonic = Mnemonic.generate(); - const encryptionKey = this.deriveEncryptionKey( - params.deviceId, - params.userId, - ); - - const wallets = new Map(); - const chains = [ChainType.KAVA, ChainType.DST, ChainType.BSC]; - const userId = UserId.create(params.userId); - - for (const chainType of chains) { - const wallet = WalletAddress.createFromMnemonic({ - userId, - chainType, - mnemonic, - encryptionKey, - }); - - wallets.set(chainType, wallet); - } - - this.logger.debug(`Generated wallet system for user: ${params.userId}`); - - return { mnemonic, wallets }; - } - - recoverWalletSystem(params: { - userId: string; - mnemonic: Mnemonic; - deviceId: string; - }): Map { - const encryptionKey = this.deriveEncryptionKey( - params.deviceId, - params.userId, - ); - - const wallets = new Map(); - const chains = [ChainType.KAVA, ChainType.DST, ChainType.BSC]; - const userId = UserId.create(params.userId); - - for (const chainType of chains) { - const wallet = WalletAddress.createFromMnemonic({ - userId, - chainType, - mnemonic: params.mnemonic, - encryptionKey, - }); - - wallets.set(chainType, wallet); - } - - this.logger.debug(`Recovered wallet system for user: ${params.userId}`); - - return wallets; - } - - deriveAddress(chainType: ChainType, mnemonic: Mnemonic): string { - const seed = Buffer.from(mnemonic.toSeed()); - const config = CHAIN_CONFIG[chainType]; - - switch (chainType) { - case ChainType.KAVA: - case ChainType.DST: - return this.deriveCosmosAddress( - seed, - config.derivationPath, - config.prefix, - ); - - case ChainType.BSC: - return this.deriveEVMAddress(seed, config.derivationPath); - - default: - throw new DomainError(`不支持的链类型: ${chainType}`); - } - } - - verifyMnemonic( - mnemonic: Mnemonic, - chainType: ChainType, - expectedAddress: string, - ): boolean { - const derivedAddress = this.deriveAddress(chainType, mnemonic); - return derivedAddress.toLowerCase() === expectedAddress.toLowerCase(); - } - - private deriveCosmosAddress( - seed: Buffer, - path: string, - prefix: string, - ): string { - const hdkey = HDKey.fromMasterSeed(seed); - const childKey = hdkey.derive(path); - - if (!childKey.publicKey) { - throw new DomainError('无法派生公钥'); - } - - const pubkey = childKey.publicKey; - const hash = createHash('sha256').update(pubkey).digest(); - const addressHash = createHash('ripemd160').update(hash).digest(); - const words = bech32.toWords(addressHash); - - return bech32.encode(prefix, words); - } - - private deriveEVMAddress(seed: Buffer, path: string): string { - const hdkey = HDKey.fromMasterSeed(seed); - const childKey = hdkey.derive(path); - - if (!childKey.privateKey) { - throw new DomainError('无法派生私钥'); - } - - // 将 Uint8Array 转换为十六进制字符串 - const privateKeyHex = '0x' + Buffer.from(childKey.privateKey).toString('hex'); - const wallet = new ethers.Wallet(privateKeyHex); - return wallet.address; - } - - encryptMnemonic(mnemonic: string, key: string): EncryptedMnemonicData { - const derivedKey = scryptSync(key, this.encryptionSalt, 32); - const iv = randomBytes(16); - const cipher = createCipheriv('aes-256-gcm', derivedKey, iv); - - let encrypted = cipher.update(mnemonic, 'utf8', 'hex'); - encrypted += cipher.final('hex'); - const authTag = cipher.getAuthTag(); - - return { - encrypted, - authTag: authTag.toString('hex'), - iv: iv.toString('hex'), - }; - } - - decryptMnemonic( - encryptedData: EncryptedMnemonicData, - key: string, - ): string { - const derivedKey = scryptSync(key, this.encryptionSalt, 32); - const iv = Buffer.from(encryptedData.iv, 'hex'); - const authTag = Buffer.from(encryptedData.authTag, 'hex'); - const decipher = createDecipheriv('aes-256-gcm', derivedKey, iv); - decipher.setAuthTag(authTag); - - let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8'); - decrypted += decipher.final('utf8'); - - return decrypted; - } - - deriveEncryptionKey(deviceId: string, userId: string): string { - const input = `${deviceId}:${userId}`; - return createHash('sha256').update(input).digest('hex'); - } -} diff --git a/backend/services/identity-service/src/infrastructure/infrastructure.module.ts b/backend/services/identity-service/src/infrastructure/infrastructure.module.ts index 496ac6f4..659505a5 100644 --- a/backend/services/identity-service/src/infrastructure/infrastructure.module.ts +++ b/backend/services/identity-service/src/infrastructure/infrastructure.module.ts @@ -1,5 +1,6 @@ import { Module, Global } from '@nestjs/common'; import { HttpModule } from '@nestjs/axios'; +import { ConfigModule } from '@nestjs/config'; import { PrismaService } from './persistence/prisma/prisma.service'; import { UserAccountRepositoryImpl } from './persistence/repositories/user-account.repository.impl'; import { MpcKeyShareRepositoryImpl } from './persistence/repositories/mpc-key-share.repository.impl'; @@ -8,14 +9,14 @@ import { RedisService } from './redis/redis.service'; import { EventPublisherService } from './kafka/event-publisher.service'; import { MpcEventConsumerService } from './kafka/mpc-event-consumer.service'; import { SmsService } from './external/sms/sms.service'; -import { WalletGeneratorServiceImpl } from './external/blockchain/wallet-generator.service.impl'; +import { BlockchainClientService } from './external/blockchain/blockchain-client.service'; import { MpcClientService, MpcWalletService } from './external/mpc'; import { MPC_KEY_SHARE_REPOSITORY } from '@/domain/repositories/mpc-key-share.repository.interface'; -import { WalletGeneratorService } from '@/domain/services/wallet-generator.service'; @Global() @Module({ imports: [ + ConfigModule, HttpModule.register({ timeout: 300000, maxRedirects: 5, @@ -33,12 +34,8 @@ import { WalletGeneratorService } from '@/domain/services/wallet-generator.servi EventPublisherService, MpcEventConsumerService, SmsService, - // WalletGeneratorService 抽象类由 WalletGeneratorServiceImpl 实现 - WalletGeneratorServiceImpl, - { - provide: WalletGeneratorService, - useExisting: WalletGeneratorServiceImpl, - }, + // BlockchainClientService 调用 blockchain-service API + BlockchainClientService, MpcClientService, MpcWalletService, ], @@ -54,8 +51,7 @@ import { WalletGeneratorService } from '@/domain/services/wallet-generator.servi EventPublisherService, MpcEventConsumerService, SmsService, - WalletGeneratorServiceImpl, - WalletGeneratorService, + BlockchainClientService, MpcClientService, MpcWalletService, ],