import { ethers } from "ethers"; import { PAGE_SIZE } from "../params"; import { ProcessedTransaction, TransactionChunk } from "../types"; export class SearchController { private txs: ProcessedTransaction[]; private pageStart: number; private pageEnd: number; private constructor( readonly address: string, txs: ProcessedTransaction[], readonly isFirst: boolean, readonly isLast: boolean, boundToStart: boolean ) { this.txs = txs; if (boundToStart) { this.pageStart = 0; this.pageEnd = Math.min(txs.length, PAGE_SIZE); } else { this.pageEnd = txs.length; this.pageStart = Math.max(0, txs.length - PAGE_SIZE); } } private static rawToProcessed = ( provider: ethers.providers.JsonRpcProvider, _rawRes: any ) => { const _res: ethers.providers.TransactionResponse[] = _rawRes.txs.map( (t: any) => provider.formatter.transactionResponse(t) ); return { txs: _res.map((t, i): ProcessedTransaction => { const _rawReceipt = _rawRes.receipts[i]; const _receipt = provider.formatter.receipt(_rawReceipt); return { blockNumber: t.blockNumber!, timestamp: provider.formatter.number(_rawReceipt.timestamp), idx: _receipt.transactionIndex, hash: t.hash, from: t.from, to: t.to, createdContractAddress: _receipt.contractAddress, value: t.value, fee: _receipt.gasUsed.mul(t.gasPrice!), gasPrice: t.gasPrice!, data: t.data, status: _receipt.status!, }; }), firstPage: _rawRes.firstPage, lastPage: _rawRes.lastPage, }; }; private static async readBackPage( provider: ethers.providers.JsonRpcProvider, address: string, baseBlock: number ): Promise { const _rawRes = await provider.send("ots_searchTransactionsBefore", [ address, baseBlock, PAGE_SIZE, ]); return this.rawToProcessed(provider, _rawRes); } private static async readForwardPage( provider: ethers.providers.JsonRpcProvider, address: string, baseBlock: number ): Promise { const _rawRes = await provider.send("ots_searchTransactionsAfter", [ address, baseBlock, PAGE_SIZE, ]); return this.rawToProcessed(provider, _rawRes); } static async firstPage( provider: ethers.providers.JsonRpcProvider, address: string ): Promise { const newTxs = await SearchController.readBackPage(provider, address, 0); return new SearchController( address, newTxs.txs, newTxs.firstPage, newTxs.lastPage, true ); } static async middlePage( provider: ethers.providers.JsonRpcProvider, address: string, hash: string, next: boolean ): Promise { const tx = await provider.getTransaction(hash); const newTxs = next ? await SearchController.readBackPage(provider, address, tx.blockNumber!) : await SearchController.readForwardPage( provider, address, tx.blockNumber! ); return new SearchController( address, newTxs.txs, newTxs.firstPage, newTxs.lastPage, next ); } static async lastPage( provider: ethers.providers.JsonRpcProvider, address: string ): Promise { const newTxs = await SearchController.readForwardPage(provider, address, 0); return new SearchController( address, newTxs.txs, newTxs.firstPage, newTxs.lastPage, false ); } getPage(): ProcessedTransaction[] { return this.txs.slice(this.pageStart, this.pageEnd); } async prevPage( provider: ethers.providers.JsonRpcProvider, hash: string ): Promise { // Already on this page if (this.txs[this.pageEnd - 1].hash === hash) { return this; } if (this.txs[this.pageStart].hash === hash) { const overflowPage = this.txs.slice(0, this.pageStart); const baseBlock = this.txs[0].blockNumber; const prevPage = await SearchController.readForwardPage( provider, this.address, baseBlock ); return new SearchController( this.address, prevPage.txs.concat(overflowPage), prevPage.firstPage, prevPage.lastPage, false ); } return this; } async nextPage( provider: ethers.providers.JsonRpcProvider, hash: string ): Promise { // Already on this page if (this.txs[this.pageStart].hash === hash) { return this; } if (this.txs[this.pageEnd - 1].hash === hash) { const overflowPage = this.txs.slice(this.pageEnd); const baseBlock = this.txs[this.txs.length - 1].blockNumber; const nextPage = await SearchController.readBackPage( provider, this.address, baseBlock ); return new SearchController( this.address, overflowPage.concat(nextPage.txs), nextPage.firstPage, nextPage.lastPage, true ); } return this; } }