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: add l1 gas estimation for l2 #299

Merged
merged 34 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
56e5926
chore: add L2Chain
npty Feb 19, 2024
9d55e13
feat: add ethereum-multicall lib
npty Feb 19, 2024
fb8fecb
feat: implement get L1 fee
npty Feb 19, 2024
b85cf21
chore: add utility function isL2Chain
npty Feb 19, 2024
fa19745
chore: add EstimateL1FeeParams type
npty Feb 19, 2024
f3b45f2
chore: update rpc node for ethereum
npty Feb 19, 2024
75fd9a3
feat: implement getL1Fee
npty Feb 19, 2024
cfb282f
chore: add types
npty Feb 20, 2024
24dbdfd
chore: make sourceChainTokenSymbol optional
npty Feb 20, 2024
27069bd
chore: refactor getL1Fee
npty Feb 20, 2024
1287539
chore: add tests for checking undefined executeData
npty Feb 20, 2024
db64d2b
chore: remove unused imports
npty Feb 20, 2024
f77f744
chore: refactor l1FeeForL2
npty Feb 20, 2024
f0ca0fb
chore: move param
npty Feb 20, 2024
038e212
chore: remove gasTokenSymbol from calculateNativeGasFee
npty Feb 20, 2024
20721e2
chore: update changelog
npty Feb 20, 2024
c16f233
chore: add function doc
npty Feb 20, 2024
bd2def5
chore: add l1ExecutionFee to response
npty Feb 20, 2024
08bb037
chore: add more tests
npty Feb 20, 2024
a943d7d
chore: cleanup tests
npty Feb 20, 2024
73d6d7e
chore: use chains from the api instead of hardcoded chains
npty Feb 23, 2024
3d482ea
chore: group l1 fee calculation
npty Feb 23, 2024
7f8917d
chore: fix build error
npty Feb 23, 2024
7844587
chore: optimize l2 gas calculations for estimateGasFee (#300)
canhtrinh Feb 29, 2024
f0a05e7
chore: executeData is 0x if not specified
npty Feb 29, 2024
1c92396
chore: fix test
npty Feb 29, 2024
78f045c
chore: remove error throw for l1 gas price
npty Feb 29, 2024
72da47e
chore: please linter
npty Feb 29, 2024
8247d79
chore: remove log
npty Feb 29, 2024
956540f
feat: use l2_type from getFees api instead of hardcoded chain
npty Feb 29, 2024
184f26a
chore: remove only
npty Feb 29, 2024
9d320b3
chore: remove unused imports
npty Feb 29, 2024
ef629ed
Merge branch 'main' into feat/add-l1-gas-estimation-for-l2
npty Mar 6, 2024
f93ff55
chore: fix md header
npty Mar 6, 2024
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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [0.15.0] - 2024-FEBRUARY-21

- Improved the accuracy of `estimateGasFee` function by incorporating L1 rollup fee for destination L2 chain.

Breaking Changes:

The `sourceChainTokenSymbol` will not be required anymore. If not specified, the native token of source chain will be used to calculate estimated fee. The following functions are affected:

- `calculateNativeGasFee`
- `estimateGasFee`

## [0.14.2] - 2024-MARCH-5

- Add configs for L2s on testnet; add configs for Blast, Fraxtal on mainnet
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"bech32": "^2.0.0",
"clone-deep": "^4.0.1",
"cross-fetch": "^3.1.5",
"ethereum-multicall": "^2.21.0",
"ethers": "^5.7.2",
"socket.io-client": "^4.6.1",
"standard-http-error": "^2.0.1",
Expand Down
14 changes: 14 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

137 changes: 114 additions & 23 deletions src/libs/AxelarQueryAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import { parseUnits } from "ethers/lib/utils";
import { loadAssets } from "../assets";
import { EnvironmentConfigs, getConfigs } from "../constants";
import { RestService } from "../services";
import { AxelarQueryAPIConfig, BaseFeeResponse, Environment } from "./types";
import {
AxelarQueryAPIConfig,
BaseFeeResponse,
Environment,
EstimateL1FeeParams,
FeeToken,
} from "./types";
import { EvmChain } from "../constants/EvmChain";
import { GasToken } from "../constants/GasToken";
import { AxelarQueryClient, AxelarQueryClientType } from "./AxelarQueryClient";
Expand All @@ -16,9 +22,12 @@ import {
import { throwIfInvalidChainIds } from "../utils";
import { loadChains } from "../chains";
import s3 from "./TransactionRecoveryApi/constants/s3";
import { BigNumber, BigNumberish } from "ethers";
import { BigNumber, BigNumberish, ethers } from "ethers";
import { ChainInfo } from "src/chains/types";
import { BigNumberUtils } from "./BigNumberUtils";
import { rpcMap as testnetRpcMap } from "./TransactionRecoveryApi/constants/chain/testnet";
import { rpcMap as mainnetRpcMap } from "./TransactionRecoveryApi/constants/chain/mainnet";
import { getL1FeeForL2 } from "./fee/getL1Fee";

interface TranslatedTransferRateLimitResponse {
incoming: string;
Expand All @@ -38,6 +47,8 @@ export interface AxelarQueryAPIFeeResponse {
baseFee: string;
executionFee: string;
executionFeeWithMultiplier: string;
l1ExecutionFeeWithMultiplier: string;
l1ExecutionFee: string;
gasMultiplier: number;
gasLimit: BigNumberish;
minGasPrice: string;
Expand Down Expand Up @@ -215,9 +226,11 @@ export class AxelarQueryAPI {
const {
source_base_fee_string,
source_token,
ethereum_token,
destination_native_token,
express_fee_string,
express_supported,
l2_type,
} = response.result;
const execute_gas_multiplier = response.result.execute_gas_multiplier as number;
const { decimals: sourceTokenDecimals } = source_token;
Expand All @@ -229,73 +242,139 @@ export class AxelarQueryAPI {
return {
baseFee,
expressFee,
sourceToken: source_token,
sourceToken: source_token as BaseFeeResponse["sourceToken"],
executeGasMultiplier: parseFloat(execute_gas_multiplier.toFixed(2)),
destToken: {
gas_price: destination_native_token.gas_price,
gas_price_gwei: parseInt(destination_native_token.gas_price_gwei).toString(),
decimals: destination_native_token.decimals,
token_price: destination_native_token.token_price,
name: destination_native_token.name,
symbol: destination_native_token.symbol,
l1_gas_price_in_units: destination_native_token.l1_gas_price_in_units,
},
l2_type,
ethereumToken: ethereum_token as BaseFeeResponse["ethereumToken"],
apiResponse: response,
success: true,
expressSupported: express_supported,
};
});
}

public async estimateL1GasFee(destChainId: EvmChain | string, l1FeeParams: EstimateL1FeeParams) {
// Retrieve the RPC URL for the source chain to calculate L1 fee
const rpcMap = this.environment === "mainnet" ? mainnetRpcMap : testnetRpcMap;

// Throw an error if the RPC URL is not found
if (!rpcMap[destChainId]) {
throw new Error(`RPC URL not found for chain ${destChainId}`);
}

const provider = new ethers.providers.JsonRpcProvider(rpcMap[destChainId]);

return getL1FeeForL2(provider, l1FeeParams);
}

public async calculateL1FeeForDestL2(
destChainId: EvmChain | string,
destToken: FeeToken,
executeData: `0x${string}` | undefined,
sourceToken: FeeToken,
ethereumToken: BaseFeeResponse["ethereumToken"],
actualGasMultiplier: number,
l2Type: BaseFeeResponse["l2_type"]
): Promise<[BigNumber, BigNumber]> {
let l1ExecutionFee = BigNumber.from(0);
let l1ExecutionFeeWithMultiplier = BigNumber.from(0);

if (destToken.l1_gas_price_in_units) {
if (!executeData) {
console.warn(
`Since you did not provide executeData, this API will not accurately calculate the
total required fee as we will not be able to capture the L1 inclusion fee for this L2 chain.`
);
}

// Calculate the L1 execution fee. This value is in ETH.
l1ExecutionFee = await this.estimateL1GasFee(destChainId, {
executeData: executeData || "0x",
l1GasPrice: destToken.l1_gas_price_in_units,
l2Type,
});

// Convert the L1 execution fee to the source token
const srcTokenPrice = Number(sourceToken.token_price.usd);
const ethTokenPrice = Number(ethereumToken.token_price.usd);
const ethToSrcTokenPriceRatio = ethTokenPrice / srcTokenPrice;

const actualL1ExecutionFee = Math.ceil(l1ExecutionFee.toNumber() * ethToSrcTokenPriceRatio);

l1ExecutionFee = BigNumber.from(actualL1ExecutionFee.toString());

// Calculate the L1 execution fee with the gas multiplier
l1ExecutionFeeWithMultiplier = BigNumber.from(
Math.floor(actualGasMultiplier * actualGasMultiplier)
);
}

return [l1ExecutionFee, l1ExecutionFeeWithMultiplier];
}

/**
* Calculate estimated gas amount to pay for the gas receiver contract.
* @param sourceChainId Can be of the EvmChain enum or string. If string, should try to generalize to use the CHAINS constants (e.g. CHAINS.MAINNET.ETHEREUM)
* @param destinationChainId Can be of the EvmChain enum or string. If string, should try to generalize to use the CHAINS constants (e.g. CHAINS.MAINNET.ETHEREUM)
* @param sourceChainTokenSymbol
* @param gasLimit An estimated gas amount required to execute `executeWithToken` function.
* @param gasMultiplier (Optional) A multiplier used to create a buffer above the calculated gas fee, to account for potential slippage throughout tx execution, e.g. 1.1 = 10% buffer. supports up to 3 decimal places
* The default value is "auto", which uses the gas multiplier from the fee response
* @param sourceChainTokenSymbol (Optional) the gas token symbol on the source chain.
* @param minGasPrice (Optional) A minimum value, in wei, for the gas price on the destination chain that is used to override the estimated gas price if it falls below this specified value.
* @param executeData (Optional) The data to be executed on the destination chain. It's recommended to specify it if the destination chain is an L2 chain to calculate more accurate gas fee.
* @param gmpParams (Optional) Additional parameters for GMP transactions, including the ability to see a detailed view of the fee response
* @returns
*/
public async estimateGasFee(
sourceChainId: EvmChain | string,
destinationChainId: EvmChain | string,
sourceChainTokenSymbol: GasToken | string,
gasLimit: BigNumberish,
gasMultiplier: number | "auto" = "auto",
sourceChainTokenSymbol?: GasToken | string,
minGasPrice = "0",
executeData?: `0x${string}`,
gmpParams?: GMPParams
): Promise<string | AxelarQueryAPIFeeResponse> {
await throwIfInvalidChainIds([sourceChainId, destinationChainId], this.environment);

const response = await this.getNativeGasBaseFee(
sourceChainId,
destinationChainId,
sourceChainTokenSymbol as GasToken,
gmpParams?.tokenSymbol,
gmpParams?.destinationContractAddress,
gmpParams?.sourceContractAddress,
gmpParams?.transferAmount,
gmpParams?.transferAmountInUnits
);
if (!BigNumber.from(gasLimit).gt(0)) {
throw new Error("Gas limit must be provided");
}

const {
baseFee,
expressFee,
sourceToken,
ethereumToken,
executeGasMultiplier,
destToken,
apiResponse,
l2_type,
success,
expressSupported,
} = response;
} = await this.getNativeGasBaseFee(
sourceChainId,
destinationChainId,
sourceChainTokenSymbol as GasToken,
gmpParams?.tokenSymbol,
gmpParams?.destinationContractAddress,
gmpParams?.sourceContractAddress,
gmpParams?.transferAmount,
gmpParams?.transferAmountInUnits
);

if (!success || !baseFee || !sourceToken) {
throw new Error("Failed to estimate gas fee");
}

if (!BigNumber.from(gasLimit).gt(0)) {
throw new Error("Gas limit must be provided");
}

const destGasFeeWei = BigNumberUtils.multiplyToGetWei(
BigNumber.from(gasLimit),
destToken.gas_price,
Expand All @@ -320,19 +399,31 @@ export class AxelarQueryAPI {
? executionFee.mul(actualGasMultiplier * 10000).div(10000)
: executionFee;

const [l1ExecutionFee, l1ExecutionFeeWithMultiplier] = await this.calculateL1FeeForDestL2(
destinationChainId,
destToken,
executeData,
sourceToken,
ethereumToken,
actualGasMultiplier,
l2_type
);

return gmpParams?.showDetailedFees
? {
baseFee,
expressFee,
executionFee: executionFee.toString(),
executionFee: executionFeeWithMultiplier.toString(),
executionFeeWithMultiplier: executionFeeWithMultiplier.toString(),
l1ExecutionFeeWithMultiplier: l1ExecutionFeeWithMultiplier.toString(),
l1ExecutionFee: l1ExecutionFee.toString(),
gasLimit,
gasMultiplier: actualGasMultiplier,
minGasPrice: minGasPrice === "0" ? "NA" : minGasPrice,
apiResponse,
isExpressSupported: expressSupported,
}
: executionFeeWithMultiplier.add(baseFee).toString();
: l1ExecutionFeeWithMultiplier.add(executionFeeWithMultiplier).add(baseFee).toString();
}

/**
Expand Down
Loading
Loading