import { Injectable , Logger } from '@nestjs/common' ; import { InjectRepository } from '@nestjs/typeorm' ; import { Repository , In } from 'typeorm' ; import { BigNumber , constants } from 'ethers' ; import { IBlockchainEvent } from '@energyweb/issuer' ; import { NewTransactionProcessedData } from './events/new-transaction-processed.event' ; import { TransactionLog } from './transaction-log.entity' ; import { BlockchainEventType } from './types' ; import { groupBy , partition } from '../../utils/array' ; interface ISaveParams { certificateId : number ; transactionHash : string ; transactionType : BlockchainEventType ; transactionTimestamp : Date ; } @ Injectable ( ) export class TransactionLogService { private readonly logger = new Logger ( TransactionLogService . name ) ; constructor ( @ InjectRepository ( TransactionLog ) private readonly repository ) { } public async logEvent ( { event , transactionType } ) { if ( this . shouldSkip ( event , transactionType ) ) { return ; } const certificateIds = this . extractEventCertificateIds ( event , transactionType ) ; const savePromises = certificateIds . map ( ( certificateId ) => this . saveTransaction ( { certificateId , transactionType , transactionHash : event . transactionHash , transactionTimestamp : event . timestamp ? new Date ( event . timestamp * 1000 ) : new Date ( ) } ) ) ; await Promise . all ( savePromises ) ; } public async findByCertificateIds ( certificateIds ) { const logs = await this . repository . find ( { where : { certificateId : In ( certificateIds . map ( ( id ) => String ( id ) ) ) } } ) ; return this . removeDuplicatedLogs ( logs ) ; } private async saveTransaction ( { certificateId , transactionHash , transactionType , transactionTimestamp } ) { const entity = this . repository . create ( { certificateId : String ( certificateId ) , transactionHash , transactionType , transactionTimestamp } ) ; await this . repository . save ( entity ) ; } private extractEventCertificateIds ( event , eventType ) { const singleId = event . _id ?? event . id ; const multipleId = event . _ids ?? event . ids ; if ( ! singleId && ! multipleId ) { this . logger . error ( `Cannot extract certificates id of ${ eventType } for creating transaction log. Probably this event should be ignored` ) ; return [ ] ; } const ids = singleId ? [ singleId ] : multipleId ; return ids . map ( ( i ) => i . toNumber ( ) ) ; } private shouldSkip ( event , eventType ) { const isTransfer = this . isTransfer ( eventType ) ; const isSupported = Object . values ( BlockchainEventType ) . includes ( eventType ) ; return ( ( isTransfer && event . from === constants . AddressZero ) || ( isTransfer && event . to === constants . AddressZero ) || ! isSupported ) ; } INFO private removeDuplicatedLogs ( logs ) { const [ suspiciousLogs , otherLogs ] = partition ( logs , ( l ) => { return this . isTransfer ( l . transactionType ) || this . isClaim ( l . transactionType ) ; } ) ; const grouped = Object . values ( groupBy ( suspiciousLogs , ( log ) => `${ log . transactionHash }--${ log . certificateId }` as string ) ) ; const filtered = grouped . map ( ( group ) => { if ( group . some ( ( l ) => this . isMultiple ( l . transactionType ) ) ) { return group . filter ( ( g ) => g . transactionType !== BlockchainEventType . TransferSingle ) ; } else { return group ; } } ) ; return [ ... otherLogs , ... filtered . flat ( ) ] ; } private isMultiple ( type ) { return [ BlockchainEventType . TransferBatchMultiple , BlockchainEventType . ClaimBatchMultiple ] . includes ( type ) ; } private isTransfer ( type ) { return [ BlockchainEventType . TransferBatch , BlockchainEventType . TransferBatchMultiple , BlockchainEventType . TransferSingle ] . includes ( type ) ; } private isClaim ( type ) { return [ BlockchainEventType . ClaimBatch , BlockchainEventType . ClaimSingle , BlockchainEventType . ClaimBatchMultiple ] . includes ( type ) ; } }