diff --git a/package.json b/package.json index 505f10b..a39f9ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@tornado/tornado-oracles", - "version": "1.2.2", + "version": "1.3.0", "description": "Gas oracle for Tornado-specific transactions", "main": "./lib/index.js", "types": "./lib/index.d.ts", diff --git a/src/feeOracle.ts b/src/feeOracle.ts index 2d6978d..9032e67 100644 --- a/src/feeOracle.ts +++ b/src/feeOracle.ts @@ -1,7 +1,15 @@ import { GasPriceOracle } from '@tornado/gas-price-oracle'; import { BigNumber, BigNumberish, ethers } from 'ethers'; import { parseUnits } from 'ethers/lib/utils'; -import { TransactionData, TxType, ITornadoFeeOracle, LegacyGasPriceKey, GasPriceParams } from './types'; +import { + TransactionData, + TxType, + ITornadoFeeOracle, + LegacyGasPriceKey, + GasPriceParams, + GetGasParamsRes, + HexadecimalStringifiedNumber, +} from './types'; import { JsonRpcProvider } from '@ethersproject/providers'; import { ChainId, defaultGasPrices, defaultInstanceTokensGasLimit, InstanceTokenSymbol } from './config'; import { bump, calculateGasPriceInWei, convertETHToToken, fromGweiToWeiHex, serializeTx } from './utils'; @@ -23,9 +31,9 @@ export abstract class TornadoFeeOracle implements ITornadoFeeOracle { * Because Optimism transaction published on Mainnet, for each OP transaction we need to calculate L1 security fee: * https://community.optimism.io/docs/developers/build/transaction-fees/#priority-fee * @param {TransactionData} tx Transaction data to estimate L1 additional fee - * @returns {Promise} Fee in WEI (MATIC) + * @returns {Promise} Fee in WEI (MATIC), '0' if chain is not Optimism */ - async fetchL1OptimismFee(tx?: TransactionData): Promise { + async fetchL1OptimismFee(tx?: TransactionData): Promise { if (this.chainId != ChainId.OPTIMISM) return BigNumber.from(0).toHexString(); const optimismL1FeeOracle = getOptimismL1FeeOracle(this.provider); @@ -35,17 +43,40 @@ export abstract class TornadoFeeOracle implements ITornadoFeeOracle { } /** - * Estimates next block gas for signed, unsigned or incomplete Tornado transaction - * @param {TransactionData} tx Transaction data in web3 / ethers format - * @param {TxType} txType Tornado transaction type: withdrawal by user, withdrawal by relayer or 'other' - * @param {LegacyGasPriceKey} speed Preferred transaction speed, if uses legacy gas (before EIP-1559) - * @returns {Promise} Gas value in WEI (hex-format) + * Estimate gas price, gas limit and l1Fee for sidechain (if exists) + * @param {TransactionData} [tx] Transaction data in web3 / ethers format + * @param {TxType} [txType=other] Tornado transaction type: withdrawal by user, withdrawal by relayer or 'other' + * @param {LegacyGasPriceKey} [speed=fast] Preferred transaction speed, if uses legacy gas (before EIP-1559) + * @returns {Promise} Object with fields 'gasPrice', 'gasLimit' and 'l1Fee' */ - async getGas(tx?: TransactionData, txType: TxType = 'other', speed: LegacyGasPriceKey = 'fast'): Promise { - const [gasPrice, gasLimit] = await Promise.all([this.getGasPrice(txType, speed), this.getGasLimit(tx, txType)]); - let gas = BigNumber.from(gasPrice).mul(gasLimit); - if (tx) tx = Object.assign(tx, { gasPrice, gasLimit }); - if (this.chainId === ChainId.OPTIMISM) gas = gas.add(await this.fetchL1OptimismFee(tx)); + async getGasParams( + tx?: TransactionData, + txType: TxType = 'other', + speed: LegacyGasPriceKey = 'fast', + ): Promise { + const [gasPrice, gasLimit, l1Fee] = await Promise.all([ + this.getGasPrice(txType, speed), + this.getGasLimit(tx, txType), + this.fetchL1OptimismFee(tx), + ]); + + return { gasLimit, gasPrice, l1Fee }; + } + + /** + * Estimates next block gas for signed, unsigned or incomplete Tornado transaction + * @param {TransactionData} [tx] Transaction data in web3 / ethers format + * @param {TxType} [txType=other] Tornado transaction type: withdrawal by user, withdrawal by relayer or 'other' + * @param {LegacyGasPriceKey} [speed=fast] Preferred transaction speed, if uses legacy gas (before EIP-1559) + * @returns {Promise} Gas value in WEI (hex-format) + */ + async getGas( + tx?: TransactionData, + txType: TxType = 'other', + speed: LegacyGasPriceKey = 'fast', + ): Promise { + const { gasPrice, gasLimit, l1Fee } = await this.getGasParams(tx, txType, speed); + const gas = BigNumber.from(gasPrice).mul(gasLimit).add(l1Fee); return gas.toHexString(); } @@ -75,13 +106,13 @@ export abstract class TornadoFeeOracle implements ITornadoFeeOracle { * @param {TxType} type Tornado transaction type (to select correct default bump percent) * @param {LegacyGasPriceKey} speed Preferred transaction speed, if uses legacy gas (before EIP-1559) * @param {number} bumpPercent Gas bump percent to prioritize transaction - * @returns {Promise} Gas price in WEI (hex string) + * @returns {Promise} Gas price in WEI (hex string) */ async getGasPrice( type: TxType = 'other', speed: LegacyGasPriceKey = 'fast', bumpPercent: number = 0, - ): Promise { + ): Promise { const gasPriceParams = await this.getGasPriceParams(type, speed, bumpPercent); return calculateGasPriceInWei(gasPriceParams).toHexString(); } @@ -101,9 +132,9 @@ export abstract class TornadoFeeOracle implements ITornadoFeeOracle { * * Refund needed that recipient can use tokens after withdrawal (covers gas fee for send/swap) * @param {InstanceTokenSymbol} tokenSymbol Withdrawal token (currency) symbol - for example, 'dai' - * @returns {Promise} Refund amount in WEI (hexed number) + * @returns {Promise} Refund amount in WEI (in hex format) */ - async calculateRefundInETH(tokenSymbol: InstanceTokenSymbol): Promise { + async calculateRefundInETH(tokenSymbol: InstanceTokenSymbol): Promise { // In Tornado we need to calculate refund only on user side, relayer get refund value in proof const gasPrice = await this.getGasPrice(); const gasLimit = defaultInstanceTokensGasLimit[tokenSymbol]; @@ -113,15 +144,15 @@ export abstract class TornadoFeeOracle implements ITornadoFeeOracle { /** * Get refund amount on ETH or Goerli in non-native token * @param {BigNumberish} tokenPriceInEth Token price in WEI in native currency - * @param {string | number} tokenDecimals Token (currency) decimals + * @param {HexadecimalStringifiedNumber | number} tokenDecimals Token (currency) decimals * @param {InstanceTokenSymbol} tokenSymbol Withdrawal token (currency) symbol - for example, 'dai' - * @returns {Promise} Refund amount in WEI in selected token (hexed number) + * @returns {Promise} Refund amount in WEI in selected token (hexed number) */ async calculateRefundInToken( tokenPriceInEth: BigNumberish, - tokenDecimals: string | number, + tokenDecimals: HexadecimalStringifiedNumber | number, tokenSymbol: InstanceTokenSymbol, - ): Promise { + ): Promise { const refundInEth = await this.calculateRefundInETH(tokenSymbol); return convertETHToToken(refundInEth, tokenDecimals, tokenPriceInEth).toHexString(); } @@ -134,22 +165,22 @@ export abstract class TornadoFeeOracle implements ITornadoFeeOracle { * @param {TransactionData} tx Transaction data (object in web3 / ethers format) * @param {number} relayerFeePercent Relayer fee percent from the transaction amount (for example, 0.15 for BNB or 0.4 for ETH Mainnet) * @param {AvailableTokenSymbols | Uppercase} currency Currency symbol - * @param {number | string } amount Withdrawal amount in selected currency - * @param {number | string } decimals Token (currency) decimals + * @param {number | HexadecimalStringifiedNumber } amount Withdrawal amount in selected currency + * @param {number | HexadecimalStringifiedNumber } decimals Token (currency) decimals * @param {BigNumberish} [refundInEth=0] Refund in ETH, if withdrawed other tokens on Mainnet (not ETH) * @param {BigNumberish} [tokenPriceInEth] If withdrawing other token on Mainnet or Goerli, need to provide token price in ETH (in WEI) - * @returns {Promise} Fee in WEI (hexed string) + * @returns {Promise} Fee in WEI (hexed string) */ async calculateWithdrawalFeeViaRelayer( type: TxType, tx: TransactionData, relayerFeePercent: number, currency: AvailableTokenSymbols | Uppercase, - amount: string | number, - decimals: string | number, + amount: HexadecimalStringifiedNumber | number, + decimals: HexadecimalStringifiedNumber | number, refundInEth: BigNumberish = 0, tokenPriceInEth?: BigNumberish, - ): Promise { + ): Promise { const gasCosts = BigNumber.from(await this.getGas(tx, type)); const relayerFee = parseUnits(amount.toString(), decimals) diff --git a/src/types.ts b/src/types.ts index e5ace0a..fc77731 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,6 +3,9 @@ import { GasPriceKey, GetTxGasParamsRes } from '@tornado/gas-price-oracle/lib/se import { AvailableTokenSymbols } from '@tornado/tornado-config'; import { InstanceTokenSymbol } from './config'; +// Type for big hexadecimal numbers, like 0x1eff87f47e37a0 +export type HexadecimalStringifiedNumber = string; + export type LegacyGasPriceKey = GasPriceKey; export type GasPriceParams = GetTxGasParamsRes; export type LegacyGasPrices = { @@ -32,27 +35,32 @@ export interface TransactionData { } export interface ITornadoFeeOracle { - getGas: (tx?: TransactionData, type?: TxType) => Promise; + getGasParams: (tx?: TransactionData, txType?: TxType, speed?: LegacyGasPriceKey) => Promise; + getGas: (tx?: TransactionData, type?: TxType) => Promise; getGasPriceParams: (type?: TxType, speed?: LegacyGasPriceKey, bumpPercent?: number) => Promise; - getGasPrice: (type?: TxType, speed?: LegacyGasPriceKey, bumpPercent?: number) => Promise; + getGasPrice: ( + type?: TxType, + speed?: LegacyGasPriceKey, + bumpPercent?: number, + ) => Promise; getGasLimit: (tx?: TransactionData, type?: TxType, bumpPercent?: number) => Promise; - fetchL1OptimismFee: (tx?: TransactionData) => Promise; - calculateRefundInETH: (tokenSymbol: InstanceTokenSymbol) => Promise; + fetchL1OptimismFee: (tx?: TransactionData) => Promise; + calculateRefundInETH: (tokenSymbol: InstanceTokenSymbol) => Promise; calculateRefundInToken: ( tokenPriceInEth: BigNumberish, - tokenDecimals: string | number, + tokenDecimals: HexadecimalStringifiedNumber | number, tokenSymbol: InstanceTokenSymbol, - ) => Promise; + ) => Promise; calculateWithdrawalFeeViaRelayer: ( type: TxType, tx: TransactionData, relayerFeePercent: number, currency: AvailableTokenSymbols, - amount: string, + amount: HexadecimalStringifiedNumber | number, decimals: number, refundInEth: BigNumberish, tokenPriceInEth?: BigNumberish, - ) => Promise; + ) => Promise; } export interface ITornadoPriceOracle { @@ -79,3 +87,10 @@ export type Token = { decimals: number; }; export type TokenPrices = { [tokenSymbol in TokenSymbol]?: BigNumberish }; + +// Reponse type for getGasParams function of fee oracle +export type GetGasParamsRes = { + gasLimit: number; + gasPrice: HexadecimalStringifiedNumber; + l1Fee: HexadecimalStringifiedNumber; +};