From 3cd7cce1c52f94276011f4581143390c26acb49e Mon Sep 17 00:00:00 2001 From: Korbinian Date: Fri, 12 Apr 2024 00:00:23 +0200 Subject: [PATCH] feat(bridge-ui): processingFee from API (#16708) Co-authored-by: jeff <113397187+cyberhorsey@users.noreply.github.com> --- .../vite-plugins/generateCustomTokenConfig.ts | 4 +- .../ProcessingFee/ProcessingFee.svelte | 13 ++-- packages/bridge-ui/src/i18n/en.json | 14 ++-- packages/bridge-ui/src/libs/chain/chains.ts | 6 ++ .../src/libs/fee/recommendProcessingFee.ts | 69 +++++++++++-------- .../src/libs/relayer/RelayerAPIService.ts | 36 ++++++++++ .../src/libs/relayer/initRelayers.ts | 20 +++++- packages/bridge-ui/src/libs/relayer/types.ts | 20 ++++++ 8 files changed, 135 insertions(+), 47 deletions(-) diff --git a/packages/bridge-ui/scripts/vite-plugins/generateCustomTokenConfig.ts b/packages/bridge-ui/scripts/vite-plugins/generateCustomTokenConfig.ts index 3418b63f92..da51b194dd 100644 --- a/packages/bridge-ui/scripts/vite-plugins/generateCustomTokenConfig.ts +++ b/packages/bridge-ui/scripts/vite-plugins/generateCustomTokenConfig.ts @@ -3,7 +3,7 @@ import { promises as fs } from 'fs'; import path from 'path'; import { Project, SourceFile, VariableDeclarationKind } from 'ts-morph'; -import configuredChainsSchema from '../../config/schemas/configuredChains.schema.json'; +import configuredCustomTokens from '../../config/schemas/configuredCustomTokens.schema.json'; import type { Token } from '../../src/libs/token/types'; import { decodeBase64ToJson } from './../utils/decodeBase64ToJson'; import { formatSourceFile } from './../utils/formatSourceFile'; @@ -40,7 +40,7 @@ export function generateCustomTokenConfig() { configuredTokenConfigFile = decodeBase64ToJson(process.env.CONFIGURED_CUSTOM_TOKENS || ''); // Valid JSON against schema - const isValid = validateJsonAgainstSchema(configuredTokenConfigFile, configuredChainsSchema); + const isValid = validateJsonAgainstSchema(configuredTokenConfigFile, configuredCustomTokens); if (!isValid) { throw new Error('encoded generateCustomTokenConfig.json is not valid.'); diff --git a/packages/bridge-ui/src/components/Bridge/SharedBridgeComponents/ProcessingFee/ProcessingFee.svelte b/packages/bridge-ui/src/components/Bridge/SharedBridgeComponents/ProcessingFee/ProcessingFee.svelte index 2ded99110c..6aa6b7f9c9 100644 --- a/packages/bridge-ui/src/components/Bridge/SharedBridgeComponents/ProcessingFee/ProcessingFee.svelte +++ b/packages/bridge-ui/src/components/Bridge/SharedBridgeComponents/ProcessingFee/ProcessingFee.svelte @@ -139,7 +139,7 @@ {#if calculatingRecommendedAmount} ETH {:else if errorCalculatingRecommendedAmount} - {$t('processing_fee.recommended.error')} + {:else} {formatEther($processingFee ?? BigInt(0))} ETH {#if $processingFee !== recommendedAmount} | {$t('common.customized')} @@ -153,7 +153,7 @@ {#if calculatingRecommendedAmount} {:else if errorCalculatingRecommendedAmount} - {$t('processing_fee.recommended.error')} + {$t('processing_fee.recommended.error')} {:else} {formatEther($processingFee ?? BigInt(0))} ETH {#if $processingFee !== recommendedAmount} | {$t('common.customized')} @@ -179,7 +179,7 @@ {#if calculatingRecommendedAmount} ETH {:else if errorCalculatingRecommendedAmount} - {$t('processing_fee.recommended.error')} + {:else} {formatEther($processingFee ?? BigInt(0))} ETH {#if $processingFee !== recommendedAmount} | {$t('common.customized')} @@ -212,7 +212,7 @@ {#if calculatingRecommendedAmount} ETH {:else if errorCalculatingRecommendedAmount} - {$t('processing_fee.recommended.error')} + {:else} {formatEther(recommendedAmount)} ETH {/if} @@ -299,7 +299,10 @@ {/if} - + { return chain; }; +export const isL2Chain = (chainId: number) => { + return chainConfig[chainId].type === LayerType.L2; +}; + export const chains: [Chain, ...Chain[]] = Object.entries(chainConfig).map(([chainId, chainConfig]) => mapChainConfigToChain(chainId, chainConfig), ) as [Chain, ...Chain[]]; diff --git a/packages/bridge-ui/src/libs/fee/recommendProcessingFee.ts b/packages/bridge-ui/src/libs/fee/recommendProcessingFee.ts index 796ab923ae..9941d79e0e 100644 --- a/packages/bridge-ui/src/libs/fee/recommendProcessingFee.ts +++ b/packages/bridge-ui/src/libs/fee/recommendProcessingFee.ts @@ -1,11 +1,9 @@ -import { getPublicClient } from '@wagmi/core'; - -import { recommendProcessingFeeConfig } from '$config'; import { NoCanonicalInfoFoundError } from '$libs/error'; +import { relayerApiServices } from '$libs/relayer'; +import { FeeTypes } from '$libs/relayer/types'; import { type Token, TokenType } from '$libs/token'; import { getTokenAddresses } from '$libs/token/getTokenAddresses'; import { getLogger } from '$libs/util/logger'; -import { config } from '$libs/wagmi'; const log = getLogger('libs:recommendedProcessingFee'); @@ -15,16 +13,6 @@ type RecommendProcessingFeeArgs = { srcChainId?: number; }; -const { - ethGasLimit, - erc20NotDeployedGasLimit, - erc20DeployedGasLimit, - erc1155DeployedGasLimit, - erc1155NotDeployedGasLimit, - erc721DeployedGasLimit, - erc721NotDeployedGasLimit, -} = recommendProcessingFeeConfig; - export async function recommendProcessingFee({ token, destChainId, @@ -33,16 +21,8 @@ export async function recommendProcessingFee({ if (!srcChainId) { return 0n; } - const destPublicClient = getPublicClient(config, { chainId: destChainId }); - - if (!destPublicClient) throw new Error('Could not get public client'); - // getGasPrice will return gasPrice as 3000000001, rather than 3000000000 - const gasPrice = await destPublicClient.getGasPrice(); - - // The gas limit for processMessage call for ETH is about ~800k. - // To make it enticing, we say 900k - let gasLimit = ethGasLimit; + let fee; if (token.type !== TokenType.ETH) { const tokenInfo = await getTokenAddresses({ token, srcChainId, destChainId }); @@ -58,29 +38,58 @@ export async function recommendProcessingFee({ } if (token.type === TokenType.ERC20) { if (isTokenAlreadyDeployed) { - gasLimit = erc20DeployedGasLimit; log(`token ${token.symbol} is already deployed on chain ${destChainId}`); + + fee = await relayerApiServices[0].recommendedProcessingFees({ + typeFilter: FeeTypes.Erc20Deployed, + destChainIDFilter: destChainId, + }); } else { - gasLimit = erc20NotDeployedGasLimit; + fee = await relayerApiServices[0].recommendedProcessingFees({ + typeFilter: FeeTypes.Erc20NotDeployed, + destChainIDFilter: destChainId, + }); log(`token ${token.symbol} is not deployed on chain ${destChainId}`); } } else if (token.type === TokenType.ERC721) { if (isTokenAlreadyDeployed) { - gasLimit = erc721DeployedGasLimit; log(`token ${token.symbol} is already deployed on chain ${destChainId}`); + fee = await relayerApiServices[0].recommendedProcessingFees({ + typeFilter: FeeTypes.Erc721Deployed, + destChainIDFilter: destChainId, + }); } else { - gasLimit = erc721NotDeployedGasLimit; log(`token ${token.symbol} is not deployed on chain ${destChainId}`); + fee = await relayerApiServices[0].recommendedProcessingFees({ + typeFilter: FeeTypes.Erc721NotDeployed, + destChainIDFilter: destChainId, + }); } } else if (token.type === TokenType.ERC1155) { if (isTokenAlreadyDeployed) { - gasLimit = erc1155DeployedGasLimit; log(`token ${token.symbol} is already deployed on chain ${destChainId}`); + fee = await relayerApiServices[0].recommendedProcessingFees({ + typeFilter: FeeTypes.Erc1155Deployed, + destChainIDFilter: destChainId, + }); } else { - gasLimit = erc1155NotDeployedGasLimit; log(`token ${token.symbol} is not deployed on chain ${destChainId}`); + fee = await relayerApiServices[0].recommendedProcessingFees({ + typeFilter: FeeTypes.Erc1155NotDeployed, + destChainIDFilter: destChainId, + }); } } + } else { + log(`Fee for ETH bridging`); + fee = await relayerApiServices[0].recommendedProcessingFees({ + typeFilter: FeeTypes.Eth, + destChainIDFilter: destChainId, + }); } - return gasPrice * gasLimit; + if (!fee) throw new Error('Unable to get fee from relayer API'); + + const feeInWei = BigInt(fee[0].amount); + log(`Recommended fee: ${feeInWei.toString()}`); + return feeInWei; } diff --git a/packages/bridge-ui/src/libs/relayer/RelayerAPIService.ts b/packages/bridge-ui/src/libs/relayer/RelayerAPIService.ts index ed44020974..f5936204bd 100644 --- a/packages/bridge-ui/src/libs/relayer/RelayerAPIService.ts +++ b/packages/bridge-ui/src/libs/relayer/RelayerAPIService.ts @@ -16,9 +16,12 @@ import { type APIRequestParams, type APIResponse, type APIResponseTransaction, + type Fee, + type FeeType, type GetAllByAddressResponse, type PaginationInfo, type PaginationParams, + type ProcessingFeeApiResponse, type RelayerBlockInfo, RelayerEventType, } from './types'; @@ -278,6 +281,39 @@ export class RelayerAPIService { }): Promise> { throw new Error('Not implemented'); } + + async recommendedProcessingFees({ + typeFilter, + destChainIDFilter, + }: { + typeFilter?: FeeType; + destChainIDFilter?: number; + }): Promise { + const requestURL = `${this.baseUrl}/recommendedProcessingFees`; + + try { + const response = await axios.get(requestURL); + + if (response.status >= 400) throw new Error('HTTP error', { cause: response }); + + let { fees } = response.data; + + if (typeFilter) { + fees = fees.filter((fee) => fee.type === typeFilter); + } + + if (destChainIDFilter !== undefined) { + fees = fees.filter((fee) => fee.destChainID === destChainIDFilter); + } + + return fees; + } catch (error) { + console.error(error); + throw new Error('Failed to fetch recommended processing fees', { + cause: error instanceof Error ? error : undefined, + }); + } + } } const _eventToTokenType = (eventType: RelayerEventType): TokenType => { diff --git a/packages/bridge-ui/src/libs/relayer/initRelayers.ts b/packages/bridge-ui/src/libs/relayer/initRelayers.ts index bb41e51d49..09757964ca 100644 --- a/packages/bridge-ui/src/libs/relayer/initRelayers.ts +++ b/packages/bridge-ui/src/libs/relayer/initRelayers.ts @@ -2,6 +2,20 @@ import { configuredRelayer } from '$relayerConfig'; import { RelayerAPIService } from './RelayerAPIService'; -export const relayerApiServices: RelayerAPIService[] = configuredRelayer.map( - (relayerConfig: { url: string }) => new RelayerAPIService(relayerConfig.url), -); +class RelayerServiceFactory { + private static instanceCache: Map = new Map(); + + public static getServices(configuredRelayers: { url: string }[]): RelayerAPIService[] { + return configuredRelayers.map((relayerConfig) => this.getService(relayerConfig.url)); + } + + private static getService(url: string): RelayerAPIService { + if (!this.instanceCache.has(url)) { + const newInstance = new RelayerAPIService(url); + this.instanceCache.set(url, newInstance); + } + return this.instanceCache.get(url)!; + } +} + +export const relayerApiServices: RelayerAPIService[] = RelayerServiceFactory.getServices(configuredRelayer); diff --git a/packages/bridge-ui/src/libs/relayer/types.ts b/packages/bridge-ui/src/libs/relayer/types.ts index dc9b30ba8f..90ed2b64ed 100644 --- a/packages/bridge-ui/src/libs/relayer/types.ts +++ b/packages/bridge-ui/src/libs/relayer/types.ts @@ -98,3 +98,23 @@ export type RelayerConfig = { export type ConfiguredRelayer = { configuredRelayer: RelayerConfig[]; }; + +export const FeeTypes = { + Eth: 'eth', + Erc20Deployed: 'erc20Deployed', + Erc20NotDeployed: 'erc20NotDeployed', + Erc721Deployed: 'erc721Deployed', + Erc721NotDeployed: 'erc721NotDeployed', + Erc1155NotDeployed: 'erc1155NotDeployed', + Erc1155Deployed: 'erc1155Deployed', +} as const; + +export type FeeType = (typeof FeeTypes)[keyof typeof FeeTypes]; + +export type Fee = { + type: FeeType; + amount: string; + destChainID: number; +}; + +export type ProcessingFeeApiResponse = { fees: Fee[] };