import { getEndpointsForNetwork, getNetworkType, Network, } from '../../networks'; import { fromBase64ToString } from '../../utils'; import { CrosschainEvent } from '../constants'; import { latestBlockQuery, latestTransactionsOfAddressQuery, latestTransactionsQuery, latestCrosschainsQuery, searchSpecificCrosschainChainIdQuery, searchSpecificCrosschainDestChainIdQuery, searchSpecificCrosschainQuery, searchSpecificCrosschainSrcChainIdQuery, specificBlockQuery, specificCrosschainQuery, specificTransactionQuery, inboundOutboundQuery, latestFundDepositQuery, searchSpecificFundDepositQuery, searchSpecificFundDepositChainIdQuery, searchSpecificFundDepositSrcChainIdQuery, searchSpecificFundDepositDestChainIdQuery, specificFundDepositQuery, latestFundPaidQuery, searchSpecificFundPaidQuery, searchSpecificFundPaidSrcChainIdQuery, specificFundPaidQuery, validatorsUptimeQuery, latestBlockQueryWithSignatures, } from '../queries'; import { BlockTypeWithTxns, CrosschainType, FundDepositType, FundPaidType, InboundOutboundMapType, PaginatedBlock, PaginatedBlockWithSignature, PaginatedCrosschain, PaginatedFundDeposit, PaginatedFundPaid, PaginatedTransaction, TransactionType, ValidatorUptime, } from '../types'; import { CancelToken, gqlFetcher, restFetcher } from '../utils'; /** * @group Router Scan Utility */ export class RouterExplorer { public readonly chainEnvironment: Network; public readonly applicationAddress: string | null; constructor( chainEnvironment: string, applicationAddress: string = '' ) { this.chainEnvironment = getNetworkType(chainEnvironment); this.applicationAddress = applicationAddress; } //Fetch latest blocks from explorer db /** * Fetches latest Blocks * @param {string} timeRange Time Range * @param {string} limit Page Limit * @param {string} offset Page Number * @return {PaginatedBlock} * @throws {Error} */ public async getLatestBlocks( timeRange: number[] = [], limit: Number = 10, offset: Number = 1, { source } = { source: CancelToken.source(), } ): Promise<{ paginatedBlock: PaginatedBlock }> { try { const data = await gqlFetcher( this.chainEnvironment, latestBlockQuery, { timeRange, limit: limit, offset: offset, }, source ); return data; } catch (e) { throw e; } } public async getLatestBlocksWithSignatures( timeRange: number[] = [], limit: Number = 10, offset: Number = 1, { source } = { source: CancelToken.source(), } ): Promise<{ paginatedBlock: PaginatedBlockWithSignature }> { try { const data = await gqlFetcher( this.chainEnvironment, latestBlockQueryWithSignatures, { timeRange, limit: limit, offset: offset, }, source ); return data; } catch (e) { throw e; } } /** * Fetches specific Block * @param {string} height BlockHeight * @return {BlockType} * @throws {Error} */ public async getBlockByHeight( height: Number, { source } = { source: CancelToken.source(), } ): Promise<{ block: BlockTypeWithTxns }> { try { const data = await gqlFetcher( this.chainEnvironment, specificBlockQuery, { height: height, source, } ); return data; } catch (e) { throw e; } } /** * Fetches latest Transactions * @param {string} timeRange Time Range * @param {string} limit Page Limit * @param {string} offset Page Number * @return {PaginatedTransaction} * @throws {Error} */ public async getLatestTransactions( timeRange: number[] = [], limit: Number = 10, offset: Number = 1, { source } = { source: CancelToken.source(), } ): Promise<{ paginatedTransaction: PaginatedTransaction }> { try { const data = await gqlFetcher( this.chainEnvironment, this.applicationAddress ? latestTransactionsOfAddressQuery : latestTransactionsQuery, { timeRange, limit: limit, offset: offset, }, source ); return data; } catch (e) { throw e; } } /** * Fetches latest Transactions for a specific address * @param {string} address Address * @param {string} limit Page Limit * @param {string} offset Page Number * @return {PaginatedTransaction} * @throws {Error} */ public async getLatestTransactionsByAddress( address: String, timeRange: number[] = [], limit: Number = 10, offset: Number = 1, { source } = { source: CancelToken.source(), } ): Promise<{ paginatedTransaction: PaginatedTransaction }> { try { const data = await gqlFetcher( this.chainEnvironment, latestTransactionsOfAddressQuery, { timeRange, address: address, limit: limit, offset: offset, }, source ); return data; } catch (e) { throw e; } } /** * Fetches specific Transaction * @param {string} hash * @return {Transaction} * @throws {Error} */ public async getTransactionByHash( hash: String, { source } = { source: CancelToken.source(), } ): Promise<{ transaction: TransactionType }> { try { const data = await gqlFetcher( this.chainEnvironment, specificTransactionQuery, { hash: hash, }, source ); return data; } catch (e) { throw e; } } /** * Fetches latest Crosschains * @param {string} timeRange Time Range * @param {string} limit Page Limit * @param {string} offset Page Number * @return {PaginatedCrosschain} * @throws {Error} */ public async getLatestCrosschains( timeRange: number[] = [], limit: Number = 10, offset: Number = 1, { source } = { source: CancelToken.source(), } ): Promise<{ paginatedCrosschain: PaginatedCrosschain }> { try { const data = await gqlFetcher( this.chainEnvironment, latestCrosschainsQuery, { address: this.applicationAddress, timeRange, limit: limit, offset: offset, }, source ); return data; } catch (e) { throw e; } } /** * Fetches specific Crosschains * @param {string} timeRange Time Range * @param {string} searchTerm Source Sender or Source Transaction Hash * @param {string} limit Page Limit * @param {string} offset Page Number * @return {PaginatedCrosschain} * @throws {Error} */ public async getCrosschainBySearch( searchTerm: String, srcChainIds: string[] = [], dstChainIds: string[] = [], timeRange: number[] = [], limit: Number = 10, offset: Number = 1, { source } = { source: CancelToken.source(), } ): Promise<{ paginatedCrosschain: PaginatedCrosschain }> { try { const data = await gqlFetcher( this.chainEnvironment, srcChainIds.length === 0 && dstChainIds.length === 0 ? searchSpecificCrosschainQuery : srcChainIds.length > 0 && dstChainIds.length > 0 ? searchSpecificCrosschainChainIdQuery : srcChainIds.length > 0 ? searchSpecificCrosschainSrcChainIdQuery : dstChainIds.length > 0 ? searchSpecificCrosschainDestChainIdQuery : searchSpecificCrosschainQuery, { sourceChainIds: srcChainIds, destinationChainIds: dstChainIds, searchTerm: searchTerm, handlerAddress: this.applicationAddress, timeRange, limit: limit, offset: offset, }, source ); return data; } catch (e) { throw e; } } /** * Fetches specific Crosschain * @param {string} formAttestationId * @return {CrosschainType} * @throws {Error} */ public async getCrosschainByAttestationId( formAttestationId: String, { source } = { source: CancelToken.source(), } ): Promise<{ crosschain: CrosschainType }> { try { const data = await gqlFetcher( this.chainEnvironment, specificCrosschainQuery, { formAttestationId: formAttestationId, }, source ); return data; } catch (e) { throw e; } } /** * Fetches latest Fund Deposits * @param {string} timeRange Time Range * @param {string} limit Page Limit * @param {string} offset Page Number * @return {PaginatedFundDeposit} * @throws {Error} */ public async getLatestFundDeposits( timeRange: number[] = [], limit: Number = 10, offset: Number = 1, { source } = { source: CancelToken.source(), } ): Promise<{ paginatedFundDeposit: PaginatedFundDeposit }> { try { const data = await gqlFetcher( this.chainEnvironment, latestFundDepositQuery, { address: this.applicationAddress, timeRange, limit: limit, offset: offset, }, source ); return data; } catch (e) { throw new Error(`Error | getLatestFundDeposits | ${e}`); } } /** * Fetches specific Fund Deposits * @param {string} timeRange Time Range * @param {string} searchTerm srcTxHash or contract or srcToken or recipient or depositor * @param {string} limit Page Limit * @param {string} offset Page Number * @return {PaginatedFundDeposit} * @throws {Error} */ public async getFundDepositBySearch( searchTerm: String, srcChainIds: string[] = [], dstChainIds: string[] = [], timeRange: number[] = [], limit: Number = 10, offset: Number = 1, { source } = { source: CancelToken.source(), } ): Promise<{ paginatedFundDeposit: PaginatedFundDeposit }> { try { const data = await gqlFetcher( this.chainEnvironment, srcChainIds.length === 0 && dstChainIds.length === 0 ? searchSpecificFundDepositQuery : srcChainIds.length > 0 && dstChainIds.length > 0 ? searchSpecificFundDepositChainIdQuery : srcChainIds.length > 0 ? searchSpecificFundDepositSrcChainIdQuery : dstChainIds.length > 0 ? searchSpecificFundDepositDestChainIdQuery : searchSpecificFundDepositQuery, { sourceChainIds: srcChainIds, destinationChainIds: dstChainIds, searchTerm: searchTerm, timeRange, limit: limit, offset: offset, }, source ); return data; } catch (e) { throw e; } } /** * Fetches specific Fund Deposit * @param {string} id * @return {FundDepositType} * @throws {Error} */ public async getFundDepositById( id: String, { source } = { source: CancelToken.source(), } ): Promise<{ fundDeposit: FundDepositType }> { try { const data = await gqlFetcher( this.chainEnvironment, specificFundDepositQuery, { id, }, source ); return data; } catch (e) { throw e; } } /** * Fetches latest Fund Paid * @param {string} timeRange Time Range * @param {string} limit Page Limit * @param {string} offset Page Number * @return {PaginatedFundPaid} * @throws {Error} */ public async getLatestFundPaids( timeRange: number[] = [], limit: Number = 10, offset: Number = 1, { source } = { source: CancelToken.source(), } ): Promise<{ paginatedFundPaid: PaginatedFundPaid }> { try { const data = await gqlFetcher( this.chainEnvironment, latestFundPaidQuery, { address: this.applicationAddress, timeRange, limit: limit, offset: offset, }, source ); return data; } catch (e) { throw e; } } /** * Fetches specific Fund Paids * @param {string} timeRange Time Range * @param {string} searchTerm srcTxHash or contract or srcToken or recipient or depositor * @param {string} limit Page Limit * @param {string} offset Page Number * @return {PaginatedFundPaid} * @throws {Error} */ public async getFundPaidBySearch( searchTerm: String, srcChainIds: string[] = [], timeRange: number[] = [], limit: Number = 10, offset: Number = 1, { source } = { source: CancelToken.source(), } ): Promise<{ paginatedFundPaid: PaginatedFundPaid }> { try { const data = await gqlFetcher( this.chainEnvironment, srcChainIds.length === 0 ? searchSpecificFundPaidQuery : srcChainIds.length > 0 ? searchSpecificFundPaidSrcChainIdQuery : searchSpecificFundPaidQuery, { sourceChainIds: srcChainIds, searchTerm: searchTerm, timeRange, limit: limit, offset: offset, }, source ); return data; } catch (e) { throw e; } } /** * Fetches specific Fund Paid * @param {string} id * @return {FundDepositType} * @throws {Error} */ public async getFundPaidById( id: String, { source } = { source: CancelToken.source(), } ): Promise<{ fundPaid: FundPaidType }> { try { const data = await gqlFetcher( this.chainEnvironment, specificFundPaidQuery, { id, }, source ); return data; } catch (e) { throw e; } } /** * Fetches specific Transaction * @param {string} middlewareContract * @param {string} inboundId * @return {InboundOutboundMapType[]} * @throws {Error} */ public async getOutboundsForInbound( inboundId: String, middlewareContract = this.applicationAddress, { source } = { source: CancelToken.source(), } ): Promise<{ outboundToInboundMap: InboundOutboundMapType[] }> { try { const data = await gqlFetcher( this.chainEnvironment, inboundOutboundQuery, { middlewareContract, inboundId, }, source ); return data; } catch (e) { throw e; } } /** * Fetches specific Transaction * @return {ValidatorUptime[]} * @throws {Error} */ public async getValidatorsUptime( { source } = { source: CancelToken.source(), } ): Promise<{ validators: ValidatorUptime[] }> { try { const data = await gqlFetcher( this.chainEnvironment, validatorsUptimeQuery, source ); return data; } catch (e) { throw e; } } /** * Fetches specific Transaction * @param {string} sourceChainId * @param {string} nonce * @return {InboundOutboundMapType[]} * @throws {Error} */ public async getBlockEventsForCrosschain( sourceChainId: string, nonce: string, crosschainEvent: CrosschainEvent ): Promise { try { const crosschainId = sourceChainId + '-' + nonce; const crosschainData = await this.getCrosschainByAttestationId( crosschainId ); const allEventHistory = [ ...crosschainData.crosschain.eventHistory, ...crosschainData.crosschain.ackRequest.eventHistory, ]; const requiredBlock = allEventHistory.find( historyEventHistory => historyEventHistory.name.toLowerCase() === crosschainEvent.toLowerCase() )?.height; if (!requiredBlock) { throw new Error( `Error | getBlockEventsForCrosschain | ${crosschainEvent} is not present this crosschain record.` ); } return this.getBlockEvents(requiredBlock.toString()); } catch (e) { throw new Error( `Error | getExecutedBlockEventsForCrosschain | ${e}` ); } } public async getCrosschainAndBlockEvents( sourceChainId: string, nonce: string, crosschainEvent: CrosschainEvent ): Promise { try { const crosschainId = sourceChainId + '-' + nonce; const crosschainData = await this.getCrosschainByAttestationId( crosschainId ); const allEventHistory = [ ...crosschainData.crosschain.eventHistory, ...crosschainData.crosschain.ackRequest.eventHistory, ]; const requiredBlock = allEventHistory.find( historyEventHistory => historyEventHistory.name.toLowerCase() === crosschainEvent.toLowerCase() )?.height; if (!requiredBlock) { throw new Error( `Error | getBlockEventsForCrosschain | ${crosschainEvent} is not present this crosschain record.` ); } const blockEvents = await this.getBlockEvents( requiredBlock.toString() ); return { blockEvents, crosschain: crosschainData, }; } catch (e) { throw new Error(`Error | getCrosschainAndBlockEvents | ${e}`); } } public async getBlockEvents(blockNumber: string): Promise { try { const tmRpc = getEndpointsForNetwork(this.chainEnvironment) .tmEndpoint; const blockData = await restFetcher( `${tmRpc}/block_results?height=${blockNumber}` ); const decoded_end_block_events = blockData.result.end_block_events.map( (eventData: any) => { eventData.attributes = eventData.attributes.map( (attribute: any) => { try { attribute.key = fromBase64ToString(attribute.key); attribute.value = fromBase64ToString(attribute.value); } catch (e) {} return attribute; } ); return eventData; } ); const decoded_begin_block_events = blockData.result.begin_block_events.map( (eventData: any) => { eventData.attributes = eventData.attributes.map( (attribute: any) => { try { attribute.key = fromBase64ToString(attribute.key); attribute.value = fromBase64ToString(attribute.value); } catch (e) {} return attribute; } ); return eventData; } ); const decoded_txs_results_events = blockData.result.txs_results.map( (txn: any) => { return txn.events.map((eventData: any) => { eventData.attributes = eventData.attributes.map( (attribute: any) => { try { attribute.key = fromBase64ToString(attribute.key); attribute.value = fromBase64ToString(attribute.value); } catch (e) {} return attribute; } ); return eventData; }); } ); const result = { end_block_events: decoded_end_block_events, begin_block_events: decoded_begin_block_events, txs_results: decoded_txs_results_events, }; return result; } catch (e) { throw new Error( `Error | getExecutedBlockEventsForCrosschain | ${e}` ); } } }