Skip to content

Commit

Permalink
wip: refactor fetches, typescript iteration
Browse files Browse the repository at this point in the history
Signed-off-by: wildmolasses <[email protected]>
Signed-off-by: Matt Solomon <[email protected]>
  • Loading branch information
wildmolasses authored and mds1 committed Jun 11, 2021
1 parent 2ddf0c3 commit d8f89bc
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 77 deletions.
1 change: 0 additions & 1 deletion zeneth-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
42 changes: 42 additions & 0 deletions zeneth-js/src/estimations.ts
Original file line number Diff line number Diff line change
@@ -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<number> => {
// 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;
}
}
92 changes: 58 additions & 34 deletions zeneth-js/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -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<number> => {
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<number> => {
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<string, { usd: number }> = 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<BigNumber> => {
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}`);
}
};
42 changes: 0 additions & 42 deletions zeneth-js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/';
Expand Down Expand Up @@ -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<number> {
// 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 ====================
Expand Down
13 changes: 13 additions & 0 deletions zeneth-js/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<GasNowResponse['data'], 'timestamp'>;

0 comments on commit d8f89bc

Please sign in to comment.