import { ethers } from 'ethers' import { findReplacementTx } from 'find-replacement-tx' import { Transaction, TransactionByHash, TransactionResult, WaitForTxReceiptParams } from '@/services/provider/@types' import { ChainId } from '@/types' import { numbers, RPC_LIST, RPC_WS_LIST, CHAINS, FALLBACK_RPC_LIST } from '@/constants' import { Options } from './@types' import { ExtendedProvider } from './ExtendedProvider' export class Provider { public provider: ExtendedProvider private readonly chainId: ChainId public constructor(options: Options) { this.provider = new ExtendedProvider(options.url, options.chainId, options.fallbackRpcs) this.chainId = options.chainId } public async waitForTxReceipt({ txHash }: WaitForTxReceiptParams): Promise { try { const multiplier = 10 const callRetryAttempt = 15 const totalAttempt = callRetryAttempt * multiplier const startBlock = (await this.provider.getBlockNumber()) - numbers.TEN const getTransactionByHash = async () => { const tx = await this.provider.getTransaction(txHash) return tx } const transaction = await this.repeatRequestUntilResult(getTransactionByHash, totalAttempt) const findTransactionReceipt = async () => { let receipt = await this.provider.getTransactionReceipt(txHash) if (!receipt) { const tx = { nonce: transaction.nonce, from: transaction.from, to: transaction.to, data: transaction.data, } const foundTx = await findReplacementTx(this.provider, startBlock, tx) if (foundTx) { receipt = await this.provider.getTransactionReceipt(foundTx.hash) } } return receipt } const receipt = await this.repeatRequestUntilResult(findTransactionReceipt, totalAttempt) return Object.assign(receipt, { value: transaction.value }) } catch (err) { throw new Error(`Provider method waitForTxReceipt has error: ${err.message}`) } } public async waitForTxConfirmations({ txHash, callback, minConfirmation = numbers.MIN_BRIDGE_CONFIRMATION, attempt = numbers.ONE, }: WaitForTxReceiptParams): Promise { try { const callRetryAttempt = 150 if (attempt === numbers.ONE) { const txResponse = await this.provider.getTransaction(txHash) await txResponse.wait(numbers.ONE) } const receipt = await this.provider.getTransactionReceipt(txHash) if (typeof callback === 'function' && receipt) { callback(receipt) } if (receipt?.confirmations >= minConfirmation) { return true } else if (attempt >= callRetryAttempt) { return false } else { attempt++ await this.sleep(CHAINS[this.chainId].blockDuration) return await this.waitForTxConfirmations({ txHash, minConfirmation, attempt, callback }) } } catch (err) { attempt++ await this.sleep(CHAINS[this.chainId].blockDuration) return await this.waitForTxConfirmations({ txHash, minConfirmation, attempt, callback }) } } private async sleep(ms: number) { return await new Promise((resolve) => setTimeout(resolve, ms)) } private async repeatRequestUntilResult( action: CallableFunction, totalAttempts: number, retryAttempt: number = numbers.ONE, ): Promise { return await new Promise((resolve, reject) => { const iteration = async (): Promise => { try { const result = await action() if (!result) { if (retryAttempt <= totalAttempts) { retryAttempt++ setTimeout(() => { // eslint-disable-next-line no-void void iteration() }, numbers.SECOND * retryAttempt) } else { return reject(new Error('Tx not minted')) } } else { resolve(result) } } catch (err) { reject(err) } } // eslint-disable-next-line no-void void iteration() }) } } class Providers { private readonly providers: Map = new Map() public getProvider(chainId: ChainId): Provider { if (!this.providers.has(chainId)) { this.providers.set(chainId, new Provider({ url: RPC_LIST[chainId], chainId, fallbackRpcs: FALLBACK_RPC_LIST[chainId] })) } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return this.providers.get(chainId)! } } const providers = new Providers() export function getProvider(chainId: ChainId): Provider { return providers.getProvider(chainId) } // @ts-expect-error let wsProvider: ethers.providers.WebSocketProvider = null export function getWSProvider(chainId: ChainId): ethers.providers.WebSocketProvider { if (!wsProvider) { wsProvider = new ethers.providers.WebSocketProvider(RPC_WS_LIST[chainId], chainId) } return wsProvider }