rwadurian/backend/services/identity-service/src/application/event-handlers/mpc-keygen-completed.handle...

166 lines
5.3 KiB
TypeScript

/**
* MPC Keygen Event Handler
*
* Handles keygen events from mpc-service:
* - KeygenStarted: Updates status in Redis to "generating"
* - KeygenCompleted: Updates status to indicate waiting for blockchain-service
* - SessionFailed: Logs error and updates status to "failed"
*
* NOTE: Address derivation is now handled by blockchain-service.
* This handler only manages status updates. The actual wallet addresses
* are saved by BlockchainWalletHandler when it receives WalletAddressCreated
* events from blockchain-service.
*/
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { RedisService } from '@/infrastructure/redis/redis.service';
import {
MpcEventConsumerService,
KeygenStartedPayload,
KeygenCompletedPayload,
SessionFailedPayload,
} from '@/infrastructure/kafka/mpc-event-consumer.service';
// Redis key prefix for keygen status
const KEYGEN_STATUS_PREFIX = 'keygen:status:';
const KEYGEN_STATUS_TTL = 60 * 60 * 24; // 24 hours
export type KeygenStatus = 'pending' | 'generating' | 'deriving' | 'completed' | 'failed';
export interface KeygenStatusData {
status: KeygenStatus;
userId: string;
mpcSessionId?: string;
publicKey?: string;
errorMessage?: string;
updatedAt: string;
}
@Injectable()
export class MpcKeygenCompletedHandler implements OnModuleInit {
private readonly logger = new Logger(MpcKeygenCompletedHandler.name);
constructor(
private readonly redisService: RedisService,
private readonly mpcEventConsumer: MpcEventConsumerService,
) {}
async onModuleInit() {
// Register event handlers
this.mpcEventConsumer.onKeygenStarted(this.handleKeygenStarted.bind(this));
this.mpcEventConsumer.onKeygenCompleted(this.handleKeygenCompleted.bind(this));
this.mpcEventConsumer.onSessionFailed(this.handleSessionFailed.bind(this));
this.logger.log('[INIT] Registered MPC event handlers (status updates only)');
}
/**
* Handle keygen started event
*
* Update Redis status to "generating"
*/
private async handleKeygenStarted(payload: KeygenStartedPayload): Promise<void> {
const { userId, mpcSessionId } = payload;
this.logger.log(`[STATUS] Keygen started: userId=${userId}, mpcSessionId=${mpcSessionId}`);
try {
const statusData: KeygenStatusData = {
status: 'generating',
userId,
mpcSessionId,
updatedAt: new Date().toISOString(),
};
await this.redisService.set(
`${KEYGEN_STATUS_PREFIX}${userId}`,
JSON.stringify(statusData),
KEYGEN_STATUS_TTL,
);
this.logger.log(`[STATUS] Keygen status updated to 'generating' for user: ${userId}`);
} catch (error) {
this.logger.error(`[ERROR] Failed to update keygen status: ${error}`, error);
}
}
/**
* Handle keygen completed event
*
* From mpc-service, keygen is complete with public key.
* Update status to "deriving" - blockchain-service will now derive addresses
* and send WalletAddressCreated event which BlockchainWalletHandler will process.
*/
private async handleKeygenCompleted(payload: KeygenCompletedPayload): Promise<void> {
const { publicKey, extraPayload } = payload;
if (!extraPayload?.userId) {
this.logger.warn('[WARN] KeygenCompleted event missing userId, skipping');
return;
}
const { userId, username } = extraPayload;
this.logger.log(`[STATUS] Keygen completed: userId=${userId}, username=${username}`);
this.logger.log(`[STATUS] Public key: ${publicKey?.substring(0, 30)}...`);
this.logger.log(`[STATUS] Waiting for blockchain-service to derive addresses...`);
try {
// Update status to "deriving" - waiting for blockchain-service
const statusData: KeygenStatusData = {
status: 'deriving',
userId,
publicKey,
updatedAt: new Date().toISOString(),
};
await this.redisService.set(
`${KEYGEN_STATUS_PREFIX}${userId}`,
JSON.stringify(statusData),
KEYGEN_STATUS_TTL,
);
this.logger.log(`[STATUS] Keygen status updated to 'deriving' for user: ${userId}`);
this.logger.log(`[STATUS] blockchain-service will derive addresses and send WalletAddressCreated event`);
} catch (error) {
this.logger.error(`[ERROR] Failed to update keygen status: ${error}`, error);
}
}
/**
* Handle session failed event
*
* When keygen fails:
* 1. Log error
* 2. Update Redis status to "failed"
*/
private async handleSessionFailed(payload: SessionFailedPayload): Promise<void> {
const { sessionType, errorMessage, extraPayload } = payload;
// Only handle keygen failures
if (sessionType !== 'keygen' && sessionType !== 'KEYGEN') {
return;
}
const userId = extraPayload?.userId || 'unknown';
this.logger.error(`[ERROR] Keygen failed for user ${userId}: ${errorMessage}`);
try {
// Update Redis status to failed
const statusData: KeygenStatusData = {
status: 'failed',
userId,
errorMessage,
updatedAt: new Date().toISOString(),
};
await this.redisService.set(
`${KEYGEN_STATUS_PREFIX}${userId}`,
JSON.stringify(statusData),
KEYGEN_STATUS_TTL,
);
this.logger.log(`[STATUS] Keygen status updated to 'failed' for user: ${userId}`);
} catch (error) {
this.logger.error(`[ERROR] Failed to update keygen failed status: ${error}`, error);
}
}
}