import { ethers } from 'ethers' import { Router__factory, RouterETH, RouterETH__factory, StargateFeeLibraryV07__factory } from '../contract' import { chainInfoMap } from '../config/chain' import polly from 'polly-js' import { newLogger } from '../utils/logger' export class StargateClient { static logger = newLogger('StargateClient') wallet: ethers.Wallet chainId: number provider: ethers.JsonRpcProvider constructor(privateKey: string, chainId: number) { this.chainId = chainId this.provider = new ethers.JsonRpcProvider(chainInfoMap[chainId].rpcUrl) this.wallet = new ethers.Wallet(privateKey, this.provider) } async getFeeInfo(fromChainId: number, toChainId: number, sendAmount: bigint) { const dummyAddress = '0x0000000000000000000000000000000000000001' const fee = StargateFeeLibraryV07__factory.connect(chainInfoMap[fromChainId].feeAddress, this.wallet) const { eqFee, eqReward, lpFee, protocolFee, lkbRemove } = await fee.getFees( 13n, 13n, toChainId, dummyAddress, sendAmount, ) const finalFee = eqFee - eqReward + lpFee + protocolFee + lkbRemove console.log('fee', ethers.formatEther(finalFee)) } async getL0GasCost(toChainId: number) { const dummyAddress = '0x0000000000000000000000000000000000000001' const router = Router__factory.connect(chainInfoMap[this.chainId].routerAddress, this.wallet) const [gasCost] = await router.quoteLayerZeroFee(toChainId, 1, dummyAddress, '0x', { dstGasForCall: 0n, dstNativeAmount: 0n, dstNativeAddr: dummyAddress, }) return (gasCost * 120n) / 100n } async bridge(toChainId: number) { const routerEth = RouterETH__factory.connect(chainInfoMap[this.chainId].ethRouterAddress, this.wallet) const lzGasCost = await this.getL0GasCost(toChainId) const { gasCost, gasPrice, gasLimit } = await this.calculateGasCost(toChainId, routerEth, lzGasCost) let cost = gasCost let limit = gasLimit return await polly() .waitAndRetry([1000 * 60, 1000 * 60 * 2, 1000 * 60 * 3]) .executeForPromise(async info => { try { const balance = await this.provider.getBalance(this.wallet.address) // the cost is not precisely calculated, should be sufficient for common L2s if (balance < gasCost + lzGasCost) throw new Error('Insufficient balance') if (info.count > 0) { StargateClient.logger.info(`${this.wallet.address}: Retry ${info.count} times`) } // add 30% gas cost for each retry cost = (cost * 120n) / 100n limit = (limit * 120n) / 100n const sendAmount = balance - lzGasCost - cost // const sendAmount = ethers.parseEther('0.01') const minReceiveAmount = (sendAmount * 995n) / 1000n const res = await routerEth.swapETH( toChainId, this.wallet.address, this.wallet.address, sendAmount, minReceiveAmount, { value: sendAmount + lzGasCost, gasLimit: limit, gasPrice }, ) const receipt = await res.wait() if (!receipt.status) { throw new Error('Transaction reverted') } return res.hash } catch (e) { StargateClient.logger.error(e.message, `Failed to bridge to ${toChainId}`) throw e } }) } async calculateGasCost(toChainId: number, routerEth: RouterETH, lzGasCost: bigint) { const feeData = await this.provider.getFeeData() const gasPrice = feeData.gasPrice const amount = ethers.parseEther('0.001') const gasLimit = await routerEth.swapETH.estimateGas( toChainId, this.wallet.address, this.wallet.address, amount, 0n, { value: amount + lzGasCost, }, ) // const gasCost = ((this.chainId === 110 ? gasPrice : ethers.parseUnits('0.6', 'gwei')) * gasLimit * 110n) / 100n return { gasCost: ethers.parseEther('0.0004'), gasPrice, gasLimit } } }