Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(bridge-ui): processingFee from API #16708

Merged
merged 6 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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.');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@
{#if calculatingRecommendedAmount}
<LoadingText mask="0.0017730224073" /> ETH
{:else if errorCalculatingRecommendedAmount}
{$t('processing_fee.recommended.error')}
<FlatAlert type="warning" message={$t('processing_fee.recommended.error')} />
{:else}
{formatEther($processingFee ?? BigInt(0))} ETH {#if $processingFee !== recommendedAmount}
<span class="text-primary-link">| {$t('common.customized')}</span>
Expand All @@ -153,7 +153,7 @@
{#if calculatingRecommendedAmount}
<LoadingText mask="0.0017730224073" />
{:else if errorCalculatingRecommendedAmount}
{$t('processing_fee.recommended.error')}
<span class="text-warning-sentiment">{$t('processing_fee.recommended.error')}</span>
{:else}
{formatEther($processingFee ?? BigInt(0))} ETH {#if $processingFee !== recommendedAmount}
<span class="text-primary-link">| {$t('common.customized')}</span>
Expand All @@ -179,7 +179,7 @@
{#if calculatingRecommendedAmount}
<LoadingText mask="0.0001" /> ETH
{:else if errorCalculatingRecommendedAmount}
{$t('processing_fee.recommended.error')}
<FlatAlert type="warning" message={$t('processing_fee.recommended.error')} />
{:else}
{formatEther($processingFee ?? BigInt(0))} ETH {#if $processingFee !== recommendedAmount}
<span class="text-primary-link">| {$t('common.customized')}</span>
Expand Down Expand Up @@ -212,7 +212,7 @@
{#if calculatingRecommendedAmount}
<LoadingText mask="0.0001" /> ETH
{:else if errorCalculatingRecommendedAmount}
{$t('processing_fee.recommended.error')}
<FlatAlert type="warning" message={$t('processing_fee.recommended.error')} />
{:else}
{formatEther(recommendedAmount)} ETH
{/if}
Expand Down Expand Up @@ -299,7 +299,10 @@
</div>
{/if}

<RecommendedFee bind:amount={recommendedAmount} bind:calculating={calculatingRecommendedAmount} />
<RecommendedFee
bind:amount={recommendedAmount}
bind:calculating={calculatingRecommendedAmount}
bind:error={errorCalculatingRecommendedAmount} />

<NoneOption
bind:enoughEth={hasEnoughEth}
Expand Down
14 changes: 7 additions & 7 deletions packages/bridge-ui/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -239,15 +239,15 @@
"ok": "Okay",
"recipient": "Recipient",
"review": "Review",
"see_results": "See results",
"sender": "Sender",
"status": "Status",
"success": "Success",
"switch_to": "Switch to",
"symbol": "Symbol",
"to": "To",
"token_id": "Token ID",
"token_standard": "Token standard",
"see_results": "See results"
"token_standard": "Token standard"
},
"custom_recipient": {
"placeholder": "Add custom recipient"
Expand Down Expand Up @@ -358,6 +358,10 @@
"token": "Token",
"transactions": "Transactions"
},
"paginator": {
"of": "of",
"page": "Page"
},
"paused_modal": {
"description": "The bridge is currently not available. Follow our official communication channels for more information. ",
"title": "Bridge under maintenance!"
Expand All @@ -381,7 +385,7 @@
},
"recommended": {
"calculating": "Calculating",
"error": "Error calculating",
"error": "Error calculating processing fee, you will need to claim manually!",
"label": "Recommended"
},
"title": "Processing fee",
Expand Down Expand Up @@ -565,9 +569,5 @@
"connecting": "Connecting",
"disconnected": "Disconnected"
}
},
"paginator": {
"page": "Page",
"of": "of"
}
}
6 changes: 6 additions & 0 deletions packages/bridge-ui/src/libs/chain/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type { Chain } from 'viem';
import { chainConfig } from '$chainConfig';
import type { ChainConfig } from '$libs/chain';

import { LayerType } from './types';

function mapChainConfigToChain(chainId: string, chainConfig: ChainConfig): Chain {
return {
id: Number(chainId),
Expand All @@ -21,6 +23,10 @@ export const chainIdToChain = (chainId: number): Chain => {
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[]];
Expand Down
69 changes: 39 additions & 30 deletions packages/bridge-ui/src/libs/fee/recommendProcessingFee.ts
Original file line number Diff line number Diff line change
@@ -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');

Expand All @@ -15,16 +13,6 @@ type RecommendProcessingFeeArgs = {
srcChainId?: number;
};

const {
ethGasLimit,
erc20NotDeployedGasLimit,
erc20DeployedGasLimit,
erc1155DeployedGasLimit,
erc1155NotDeployedGasLimit,
erc721DeployedGasLimit,
erc721NotDeployedGasLimit,
} = recommendProcessingFeeConfig;

export async function recommendProcessingFee({
token,
destChainId,
Expand All @@ -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 });
Expand All @@ -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;
}
36 changes: 36 additions & 0 deletions packages/bridge-ui/src/libs/relayer/RelayerAPIService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -278,6 +281,39 @@ export class RelayerAPIService {
}): Promise<Record<number, RelayerBlockInfo>> {
throw new Error('Not implemented');
}

async recommendedProcessingFees({
typeFilter,
destChainIDFilter,
}: {
typeFilter?: FeeType;
destChainIDFilter?: number;
}): Promise<Fee[]> {
const requestURL = `${this.baseUrl}/recommendedProcessingFees`;

try {
const response = await axios.get<ProcessingFeeApiResponse>(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 => {
Expand Down
20 changes: 17 additions & 3 deletions packages/bridge-ui/src/libs/relayer/initRelayers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, RelayerAPIService> = 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);
20 changes: 20 additions & 0 deletions packages/bridge-ui/src/libs/relayer/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] };
Loading