import { StatRepository } from "../../repositories"; import { solana } from "../../entities"; import winston from "winston"; /** * Handling means mapping and forward to a given target if present. */ export class HandleSolanaTransactions { cfg: HandleSolanaTxConfig; mapper: (txs: solana.Transaction, args: { programId: string }) => Promise; target?: (parsed: T[], chain: string) => Promise; logger: winston.Logger = winston.child({ module: "HandleSolanaTransaction" }); statsRepo?: StatRepository; constructor( cfg: HandleSolanaTxConfig, mapper: (tx: solana.Transaction) => Promise, target?: (parsed: T[], chain: string) => Promise, statsRepo?: StatRepository ) { this.cfg = cfg; this.mapper = mapper; this.target = target; this.statsRepo = statsRepo; } public async handle(txs: solana.Transaction[]): Promise { const filteredItems = txs.filter((tx) => { const hasError = tx.meta?.err; if (hasError) this.logger.warn( `Ignoring tx for program ${this.cfg.programId} in ${tx.slot} has error: ${JSON.stringify( tx.meta?.err )}` ); return !hasError; }); let mappedItems: T[] = []; for (const tx of filteredItems) { const result = await this.mapper(tx, { programId: this.cfg.programId }); if (result.length) { this.report(); mappedItems = mappedItems.concat(result); } } if (this.target) { await this.target(mappedItems, this.cfg.chain); } else { this.logger.warn(`No target for ${this.cfg.programId} txs`); } return mappedItems; } private report() { const labels = { job: this.cfg.id, chain: this.cfg.chain ?? "", commitment: this.cfg.commitment, }; this.statsRepo!.count(this.cfg.metricName, labels); } } export type HandleSolanaTxConfig = { metricName: string; programId: string; commitment: string; chainId: number; chain: string; abi: string; id: string; };