StargateClient.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. import { ethers } from 'ethers'
  2. import { Router__factory, RouterETH, RouterETH__factory, StargateFeeLibraryV07__factory } from '../contract'
  3. import { chainInfoMap } from '../config/chain'
  4. import polly from 'polly-js'
  5. import { newLogger } from '../utils/logger'
  6. export class StargateClient {
  7. static logger = newLogger('StargateClient')
  8. wallet: ethers.Wallet
  9. chainId: number
  10. provider: ethers.JsonRpcProvider
  11. constructor(privateKey: string, chainId: number) {
  12. this.chainId = chainId
  13. this.provider = new ethers.JsonRpcProvider(chainInfoMap[chainId].rpcUrl)
  14. this.wallet = new ethers.Wallet(privateKey, this.provider)
  15. }
  16. async getFeeInfo(fromChainId: number, toChainId: number, sendAmount: bigint) {
  17. const dummyAddress = '0x0000000000000000000000000000000000000001'
  18. const fee = StargateFeeLibraryV07__factory.connect(chainInfoMap[fromChainId].feeAddress, this.wallet)
  19. const { eqFee, eqReward, lpFee, protocolFee, lkbRemove } = await fee.getFees(
  20. 13n,
  21. 13n,
  22. toChainId,
  23. dummyAddress,
  24. sendAmount,
  25. )
  26. const finalFee = eqFee - eqReward + lpFee + protocolFee + lkbRemove
  27. console.log('fee', ethers.formatEther(finalFee))
  28. }
  29. async getL0GasCost(toChainId: number) {
  30. const dummyAddress = '0x0000000000000000000000000000000000000001'
  31. const router = Router__factory.connect(chainInfoMap[this.chainId].routerAddress, this.wallet)
  32. const [gasCost] = await router.quoteLayerZeroFee(toChainId, 1, dummyAddress, '0x', {
  33. dstGasForCall: 0n,
  34. dstNativeAmount: 0n,
  35. dstNativeAddr: dummyAddress,
  36. })
  37. return (gasCost * 120n) / 100n
  38. }
  39. async bridge(toChainId: number) {
  40. const routerEth = RouterETH__factory.connect(chainInfoMap[this.chainId].ethRouterAddress, this.wallet)
  41. const lzGasCost = await this.getL0GasCost(toChainId)
  42. const { gasCost, gasPrice, gasLimit } = await this.calculateGasCost(toChainId, routerEth, lzGasCost)
  43. let cost = gasCost
  44. let limit = gasLimit
  45. return await polly()
  46. .waitAndRetry([1000 * 60, 1000 * 60 * 2, 1000 * 60 * 3])
  47. .executeForPromise(async info => {
  48. try {
  49. const balance = await this.provider.getBalance(this.wallet.address)
  50. // the cost is not precisely calculated, should be sufficient for common L2s
  51. if (balance < gasCost + lzGasCost) throw new Error('Insufficient balance')
  52. if (info.count > 0) {
  53. StargateClient.logger.info(`${this.wallet.address}: Retry ${info.count} times`)
  54. }
  55. // add 30% gas cost for each retry
  56. cost = (cost * 120n) / 100n
  57. limit = (limit * 120n) / 100n
  58. const sendAmount = balance - lzGasCost - cost
  59. // const sendAmount = ethers.parseEther('0.01')
  60. const minReceiveAmount = (sendAmount * 995n) / 1000n
  61. const res = await routerEth.swapETH(
  62. toChainId,
  63. this.wallet.address,
  64. this.wallet.address,
  65. sendAmount,
  66. minReceiveAmount,
  67. { value: sendAmount + lzGasCost, gasLimit: limit, gasPrice },
  68. )
  69. const receipt = await res.wait()
  70. if (!receipt.status) {
  71. throw new Error('Transaction reverted')
  72. }
  73. return res.hash
  74. } catch (e) {
  75. StargateClient.logger.error(e.message, `Failed to bridge to ${toChainId}`)
  76. throw e
  77. }
  78. })
  79. }
  80. async calculateGasCost(toChainId: number, routerEth: RouterETH, lzGasCost: bigint) {
  81. const feeData = await this.provider.getFeeData()
  82. const gasPrice = feeData.gasPrice
  83. const amount = ethers.parseEther('0.001')
  84. const gasLimit = await routerEth.swapETH.estimateGas(
  85. toChainId,
  86. this.wallet.address,
  87. this.wallet.address,
  88. amount,
  89. 0n,
  90. {
  91. value: amount + lzGasCost,
  92. },
  93. )
  94. // const gasCost = ((this.chainId === 110 ? gasPrice : ethers.parseUnits('0.6', 'gwei')) * gasLimit * 110n) / 100n
  95. return { gasCost: ethers.parseEther('0.0004'), gasPrice, gasLimit }
  96. }
  97. }