diff --git a/zeneth-js/package.json b/zeneth-js/package.json index ca4d9ce..8710c63 100644 --- a/zeneth-js/package.json +++ b/zeneth-js/package.json @@ -25,7 +25,6 @@ "@ethereumjs/common": "^2.2.0", "@ethereumjs/tx": "^3.1.4", "@flashbots/ethers-provider-bundle": "~0.3.1", - "axios": "^0.21.1", "ethers": "^5.1.0" }, "devDependencies": { diff --git a/zeneth-js/src/estimations.ts b/zeneth-js/src/estimations.ts new file mode 100644 index 0000000..52ccb5b --- /dev/null +++ b/zeneth-js/src/estimations.ts @@ -0,0 +1,42 @@ + + /** + * @notice Estimates the fee (in base token units) for a bundle + * @param token Token in which fee will be expressed as units of + * @param transferUpperBound Given upper bound gas fee (in Gwei) for transfer + * @param approveUpperBound Given upper bound gas fee (in Gwei) for approve + * @param swapUpperBound Given upper bound gas fee (in Gwei) for swap + * @param flashbotsAdjustment Multiplier for flashbots bribing miner. + * + * @returns Estimated fee for miner in token base units + */ + export const estimateFee = async ( + token: string, + transferUpperBound: number, + approveUpperBound: number, + swapUpperBound: number, + flashbotsAdjustment: number + ): Promise => { + // get current gas price + const gasPriceInWei = await getGasPrice(); + const { tokenPrice, ethPrice } = await getTokenAndEthPriceInUSD(token); + + const bundleGasUsed = transferUpperBound + approveUpperBound + swapUpperBound; + const initiallyCalculatedFee = bundleGasUsed * gasPriceInWei; + const bundleGasEtimateinWei = initiallyCalculatedFee * flashbotsAdjustment; + const amountOfEthUsedInGas = bundleGasEtimateinWei / 1e18; + const dollarsNeededForBribe = amountOfEthUsedInGas / ethPrice; + const tokensNeededForBribe = dollarsNeededForBribe * tokenPrice; + + console.log(`token: ${token}`); + console.log(`gas price in wei: ${gasPriceInWei}`); + console.log(`token price and eth price: ${tokenPrice}, ${ethPrice}`); + console.log(`Bundle gas used is: ${bundleGasUsed}`); + console.log(`Initially Estimated fee in Wei is: ${initiallyCalculatedFee}`); + console.log(`Estimated fee in Wei (after flashbots adjustment): ${bundleGasEtimateinWei}`); + console.log(`Amount of Eth used in Gas: ${amountOfEthUsedInGas}`); + console.log(`Amount of dollars needed for bribe: ${dollarsNeededForBribe}`); + console.log(`Amount of tokens needed for bribe: ${tokensNeededForBribe}`); + + return bundleGasEtimateinWei; + } +} diff --git a/zeneth-js/src/helpers.ts b/zeneth-js/src/helpers.ts index 2d3ac28..276e7d1 100644 --- a/zeneth-js/src/helpers.ts +++ b/zeneth-js/src/helpers.ts @@ -1,41 +1,65 @@ -import axios from 'axios'; +import { GasNowSpeed, GasNowResponse } from './types'; +import { BigNumber } from '@ethersproject/bignumber'; -/** - * @notice Gets the current gas price via Gasnow API - */ -export const getGasPrice = async (): Promise => { +const jsonFetch = (url: string) => fetch(url).then((res) => res.json()); - // get current gas price from GasNow - const gasPriceUrl = 'https://www.gasnow.org/api/v3/gas/price'; - const gasPriceInWei = await axios - .get(gasPriceUrl) - .then((response) => response.data.data.rapid) // 99 percent likelihood of tx getting included in next block - .catch((error) => { - throw new Error(`Error on GasNow API: ${error.message}`); - }); - - return gasPriceInWei +const tokenAddressToCoinGeckoId = { + '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE': 'ethereum', + '0x0D8775F648430679A709E98d2b0Cb6250d2887EF': 'basic-attention-token', + '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984': 'uniswap', + '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599': 'bitcoin', + '0x6B3595068778DD592e39A122f4f5a5cF09C90fE2': 'sushi', + '0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0': 'matic-network', + '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2': 'ethereum', + '0xD56daC73A4d6766464b38ec6D91eB45Ce7457c44': 'panvala-pan', + '0x536381a8628dBcC8C70aC9A30A7258442eAb4c92': 'pantos', + '0x514910771AF9Ca656af840dff83E8264EcF986CA': 'chainlink', + '0xBBbbCA6A901c926F240b89EacB641d8Aec7AEafD': 'loopring', +}; + +const stablecoins = [ + '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI + '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC + '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT +]; + +function isSupportedToken(tokenAddress: string): tokenAddress is keyof typeof tokenAddressToCoinGeckoId { + return Object.keys(tokenAddressToCoinGeckoId).includes(tokenAddress); } /** - * @notice Gets the given token price and Eth price in USD via Coingecko API - * @param token symbol of token of which to fetch price + * @notice Takes a token address or 0xEeEEeeee... and returns the price + * @param tokenAddress Checksummed token address */ -export const getTokenAndEthPriceInUSD = async (token: string): Promise<{tokenPrice: number, ethPrice: number}> => { - - // get current gas price from GasNow console.log("getting token and eth price") - const coingeckoUrl = `https://api.coingecko.com/api/v3/simple/price?ids=${token},ethereum&vs_currencies=usd,usd`; - let tokenPrice: number = 0; - let ethPrice: number = 0; - await axios - .get(coingeckoUrl) - .then((response) => { response.data.dai.usd, response.data.ethereum.usd - ethPrice = response.data.ethereum.usd - tokenPrice = response.data.dai.usd - }) - .catch((error: { message: any; }) => { - throw new Error(`Error on coingecko API: ${error.message}`); - }); - return {tokenPrice, ethPrice} -} +export const getTokenPriceInUsd = async (tokenAddress: string): Promise => { + try { + // Return 1 for all stablecoins + if (stablecoins.includes(tokenAddress)) return 1; + // Throw if token does not have a CoinGecko mapping + if (!isSupportedToken(tokenAddress)) throw new Error(`Unsupported token address ${tokenAddress}`); + + // Otherwise fetch price and return it + const coinGeckoId = tokenAddressToCoinGeckoId[tokenAddress]; + const coingeckoUrl = `https://api.coingecko.com/api/v3/simple/price?ids=${coinGeckoId}&vs_currencies=usd`; + const response: Record = await jsonFetch(coingeckoUrl); + return response[coinGeckoId].usd; + } catch (e) { + throw new Error(`Error fetching price for ${tokenAddress}: ${e.message as string}`); + } +}; + +/** + * @notice Gets the current gas price via Gasnow API + * @param gasPriceSpeed string of gas price speed from GasNow (e.g. `'rapid'`) + */ +export const getGasPrice = async (gasPriceSpeed: GasNowSpeed = 'rapid'): Promise => { + const gasPriceUrl = 'https://www.gasnow.org/api/v3/gas/price'; + try { + const response: GasNowResponse = await jsonFetch(gasPriceUrl); + const gasPriceInWei = response.data[gasPriceSpeed]; + return BigNumber.from(gasPriceInWei); + } catch (e) { + throw new Error(`Error on GasNow API: ${e.message as string}`); + } +}; diff --git a/zeneth-js/src/index.ts b/zeneth-js/src/index.ts index c6688f1..8adfa7e 100644 --- a/zeneth-js/src/index.ts +++ b/zeneth-js/src/index.ts @@ -7,7 +7,6 @@ import { FlashbotsBundleProvider, FlashbotsOptions } from '@flashbots/ethers-pro import { TransactionFactory } from '@ethereumjs/tx'; import Common from '@ethereumjs/common'; import { TransactionFragment } from './types'; -import { getGasPrice, getTokenAndEthPriceInUSD } from './helpers'; // Define constants const GOERLI_RELAY_URL = 'https://relay-goerli.flashbots.net/'; @@ -158,47 +157,6 @@ export class ZenethRelayer { // No errors simulating, so send the bundle return targetBlocks.map((block) => this.flashbotsProvider.sendRawBundle(signedBundle, block, opts)); } - - /** - * @notice Estimates the fee (in base token units) for a bundle - * @param token Token in which fee will be expressed as units of - * @param transferUpperBound Given upper bound gas fee (in Gwei) for transfer - * @param approveUpperBound Given upper bound gas fee (in Gwei) for approve - * @param swapUpperBound Given upper bound gas fee (in Gwei) for swap - * @param flashbotsAdjustment Multiplier for flashbots bribing miner. - * - * @returns Estimated fee for miner in token base units - */ - async estimateFee( - token: string, - transferUpperBound: number, - approveUpperBound: number, - swapUpperBound: number, - flashbotsAdjustment: number - ): Promise { - // get current gas price - const gasPriceInWei = await getGasPrice(); - const { tokenPrice, ethPrice } = await getTokenAndEthPriceInUSD(token); - - const bundleGasUsed = transferUpperBound + approveUpperBound + swapUpperBound; - const initiallyCalculatedFee = bundleGasUsed * gasPriceInWei; - const bundleGasEtimateinWei = initiallyCalculatedFee * flashbotsAdjustment; - const amountOfEthUsedInGas = bundleGasEtimateinWei / 1e18 - const dollarsNeededForBribe = amountOfEthUsedInGas / ethPrice - const tokensNeededForBribe = dollarsNeededForBribe * tokenPrice - - console.log(`token: ${token}`); - console.log(`gas price in wei: ${gasPriceInWei}`); - console.log(`token price and eth price: ${tokenPrice}, ${ethPrice}`) - console.log(`Bundle gas used is: ${bundleGasUsed}`); - console.log(`Initially Estimated fee in Wei is: ${initiallyCalculatedFee}`); - console.log(`Estimated fee in Wei (after flashbots adjustment): ${bundleGasEtimateinWei}`); - console.log(`Amount of Eth used in Gas: ${amountOfEthUsedInGas}`); - console.log(`Amount of dollars needed for bribe: ${dollarsNeededForBribe}`); - console.log(`Amount of tokens needed for bribe: ${tokensNeededForBribe}`); - - return bundleGasEtimateinWei; - } } // ==================== Helper methods ==================== diff --git a/zeneth-js/src/types.ts b/zeneth-js/src/types.ts index 98d39f1..04bf1ac 100644 --- a/zeneth-js/src/types.ts +++ b/zeneth-js/src/types.ts @@ -12,3 +12,16 @@ export interface TransactionFragment { to: string; value: BigNumberish; } + +export type GasNowResponse = { + code: number; + data: { + rapid: number; + fast: number; + standard: number; + slow: number; + timestamp: number; + }; +}; + +export type GasNowSpeed = keyof Omit;