From 948f56f8af844403aadd0f50f26b8ecf957ea783 Mon Sep 17 00:00:00 2001 From: valia fetisov Date: Mon, 4 Jul 2022 18:31:15 +0200 Subject: [PATCH] feat: RPC_URL support (#330) --- .drone.yml | 14 +-- bot/README.md | 5 +- bot/src/auctions.ts | 2 +- bot/src/authorisation.ts | 33 +++-- bot/src/index.ts | 23 ++-- bot/src/keeper.ts | 31 ++--- bot/src/variables.ts | 2 +- bot/src/whitelist.ts | 8 +- core/src/addresses.ts | 2 +- core/src/calleeFunctions/helpers/uniswapV2.ts | 2 +- core/src/constants/CALLEES.ts | 4 +- core/src/constants/NETWORKS.ts | 90 ------------- core/src/date.ts | 2 +- core/src/execute.ts | 2 +- core/src/gas.ts | 2 +- core/src/network.ts | 118 ++++++++++++++++++ core/src/provider.ts | 2 +- core/src/rpc.ts | 44 +++++++ core/src/signer.ts | 2 +- core/src/time.ts | 2 +- core/src/types.ts | 1 + docker-compose.yaml | 4 +- frontend/Dockerfile | 6 +- frontend/README.md | 4 +- frontend/components/AuctionEventsBlock.vue | 2 +- frontend/components/CalleeTable.stories.js | 24 +--- .../DashboardAuctionsView.stories.js | 12 +- frontend/components/GasTable.vue | 2 +- frontend/components/MainFlow.vue | 2 +- frontend/components/layout/Header.vue | 10 +- .../modals/ChangePageNetworkModal.vue | 21 ++-- .../modals/ChangeWalletNetworkModal.vue | 22 ++-- .../panels/WalletDaiDepositCheckPanel.vue | 7 +- frontend/components/utils/FormatAddress.vue | 10 +- .../utils/NetworkSelector.stories.js | 13 +- frontend/components/utils/NetworkSelector.vue | 22 ++-- .../wallet/WalletDepositWithdrawBlock.vue | 7 +- frontend/components/wallet/WalletModal.vue | 2 +- frontend/components/wallet/WalletTable.vue | 2 +- frontend/helpers/generateFakeCallee.ts | 25 ++++ frontend/helpers/generateFakeNetwork.ts | 23 ++++ frontend/layouts/default.vue | 10 +- frontend/lib/wallets/MetaMask.ts | 5 +- frontend/nuxt.config.js | 3 +- frontend/plugins/vuex-persist.client.js | 7 +- frontend/store/network.ts | 83 ++++++++---- frontend/store/wallet.ts | 1 - 47 files changed, 407 insertions(+), 313 deletions(-) delete mode 100644 core/src/constants/NETWORKS.ts create mode 100644 core/src/network.ts create mode 100644 core/src/rpc.ts create mode 100644 frontend/helpers/generateFakeCallee.ts create mode 100644 frontend/helpers/generateFakeNetwork.ts diff --git a/.drone.yml b/.drone.yml index 5d8354155..9ce4de1c6 100644 --- a/.drone.yml +++ b/.drone.yml @@ -16,14 +16,12 @@ data: build_args: - key: PRODUCTION_DOMAIN value_from_secret: auction-ui/main.auction-ui.k8s.sidestream.tech/frontend/production_domain - - key: INFURA_PROJECT_ID - value_from_secret: auction-ui/main.auction-ui.k8s.sidestream.tech/frontend/infura_project_id + - key: RPC_URL + value_from_secret: auction-ui/main.auction-ui.k8s.sidestream.tech/frontend/rpc_url - key: CONTACT_EMAIL value_from_secret: auction-ui/main.auction-ui.k8s.sidestream.tech/frontend/contact_email - key: STAGING_BANNER_URL value_from_secret: auction-ui/main.auction-ui.k8s.sidestream.tech/frontend/staging_banner_url - - key: DEFAULT_ETHEREUM_NETWORK - value_from_secret: auction-ui/main.auction-ui.k8s.sidestream.tech/frontend/default_etherum_network - key: HEAPIO_ID value_from_secret: auction-ui/main.auction-ui.k8s.sidestream.tech/frontend/heapio_id tags_to_cache_from: @@ -59,7 +57,7 @@ data: build_args: - key: PRODUCTION_DOMAIN value: "null.sidestream.tech" - - key: INFURA_PROJECT_ID + - key: RPC_URL value: "key-not-provided" - key: CONTACT_EMAIL value: "development@sidestream.tech" @@ -82,12 +80,10 @@ data: build_args: - key: PRODUCTION_DOMAIN value_from_secret: auction-ui/production.auction-ui.k8s.sidestream.tech/frontend/production_domain - - key: INFURA_PROJECT_ID - value_from_secret: auction-ui/production.auction-ui.k8s.sidestream.tech/frontend/infura_project_id + - key: RPC_URL + value_from_secret: auction-ui/production.auction-ui.k8s.sidestream.tech/frontend/rpc_url - key: CONTACT_EMAIL value_from_secret: auction-ui/production.auction-ui.k8s.sidestream.tech/frontend/contact_email - - key: DEFAULT_ETHEREUM_NETWORK - value_from_secret: auction-ui/production.auction-ui.k8s.sidestream.tech/frontend/default_etherum_network - key: HEAPIO_ID value_from_secret: auction-ui/production.auction-ui.k8s.sidestream.tech/frontend/heapio_id tags_to_cache_from: diff --git a/bot/README.md b/bot/README.md index 107751522..10c1cb159 100644 --- a/bot/README.md +++ b/bot/README.md @@ -27,12 +27,9 @@ $ npm run start ## Environment variables -- `INFURA_PROJECT_ID`: (required) [infura](https://infura.io/) project id (can be found in: dashboard -> ethereum -> - create new project -> settings -> keys). Note: this project can not be restricted by the origin. -- `ETHEREUM_NETWORK`: (optional, default `kovan`) – internal network name on which the bot poll for auctions. Available +- `RPC_URL`: (required) Etherium RPC url used for fetching data from the blockchain and participating in the auctions - `WHITELISTED_COLLATERALS`: (optional) a comma-separated list of collaterals the bot will fetch. Example: `MATIC-A, UNI-A` - `MAX_PRIORITY_FEE_PER_GAS_WEI`: (optional, default can be found in core/src/gas.ts) – [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) `max_priority_fee_per_gas` value - options can be found in [constants/NETWORKS](../core/src/constants/NETWORKS.ts) - `REFETCH_INTERVAL`: (optional, default 60 seconds) – interval between auction fetching requests - `KEEPER_*`: (optional) set of env variables to enable keeper bot: - `KEEPER_WALLET_PRIVATE_KEY`: (required) The wallet private key (https://metamask.zendesk.com/hc/en-us/articles/360015289632-How-to-Export-an-Account-Private-Key) diff --git a/bot/src/auctions.ts b/bot/src/auctions.ts index 7ae1cc4f6..dedd3c07d 100644 --- a/bot/src/auctions.ts +++ b/bot/src/auctions.ts @@ -25,7 +25,7 @@ export const getNewAuctionsFromActiveAuctions = function (activeActions: Auction }; export const getAllAuctions = async function (network: string): Promise { - const collaterals = await getWhitelistedCollaterals(); + const collaterals = await getWhitelistedCollaterals(network); const auctions = await fetchAllInitialAuctions(network, collaterals); const auctionIds = auctions.map(auction => `"${auction.id}"`).join(', '); diff --git a/bot/src/authorisation.ts b/bot/src/authorisation.ts index c275b9289..01bc0b58e 100644 --- a/bot/src/authorisation.ts +++ b/bot/src/authorisation.ts @@ -5,11 +5,11 @@ import { getCollateralAuthorizationStatus, getWalletAuthorizationStatus, } from 'auctions-core/src/authorizations'; -import { ETHEREUM_NETWORK, KEEPER_PREAUTHORIZE } from './variables'; +import { KEEPER_PREAUTHORIZE } from './variables'; import { getWhitelistedCollaterals } from './whitelist'; -export async function checkAndAuthorizeWallet(walletAddress: string): Promise { - const isWalletAuth = await getWalletAuthorizationStatus(ETHEREUM_NETWORK, walletAddress); +export async function checkAndAuthorizeWallet(network: string, walletAddress: string): Promise { + const isWalletAuth = await getWalletAuthorizationStatus(network, walletAddress); if (isWalletAuth) { console.info(`keeper: wallet "${walletAddress}" has already been authorized`); @@ -17,14 +17,18 @@ export async function checkAndAuthorizeWallet(walletAddress: string): Promise { +export async function checkAndAuthorizeCollateral( + network: string, + walletAddress: string, + collateralType: string +): Promise { // get collateral authorization status - const isCollateralAuth = await getCollateralAuthorizationStatus(ETHEREUM_NETWORK, collateralType, walletAddress); + const isCollateralAuth = await getCollateralAuthorizationStatus(network, collateralType, walletAddress); // try to authorize the collateral then return if (isCollateralAuth) { @@ -37,33 +41,28 @@ export async function checkAndAuthorizeCollateral(walletAddress: string, collate console.info( `keeper: collateral "${collateralType}" has not been authorized on wallet "${walletAddress}" yet. Attempting authorization now...` ); - const collateralTransactionHash = await authorizeCollateral( - ETHEREUM_NETWORK, - walletAddress, - collateralType, - false - ); + const collateralTransactionHash = await authorizeCollateral(network, walletAddress, collateralType, false); console.info( `keeper: collateral "${collateralType}" successfully authorized on wallet "${walletAddress}" via "${collateralTransactionHash}" transaction` ); return true; } -export async function executePreAuthorizationsIfRequested() { +export async function executePreAuthorizationsIfRequested(network: string) { if (KEEPER_PREAUTHORIZE !== true) { return; } - const collaterals = await getWhitelistedCollaterals(); + const collaterals = await getWhitelistedCollaterals(network); console.info( `keeper: "KEEPER_PREAUTHORIZE" is true. Attempting to authorize wallet and collaterals: "${collaterals.join( ', ' )}"` ); - const signer = await getSigner(ETHEREUM_NETWORK); + const signer = await getSigner(network); const walletAddress = await signer.getAddress(); - await checkAndAuthorizeWallet(walletAddress); + await checkAndAuthorizeWallet(network, walletAddress); for (const collateral of collaterals) { - await checkAndAuthorizeCollateral(walletAddress, collateral); + await checkAndAuthorizeCollateral(network, walletAddress, collateral); } } diff --git a/bot/src/index.ts b/bot/src/index.ts index 04e46c636..76109fce3 100644 --- a/bot/src/index.ts +++ b/bot/src/index.ts @@ -1,9 +1,9 @@ import { setTimeout as delay } from 'timers/promises'; -import { getNetworkConfigByType } from 'auctions-core/src/constants/NETWORKS'; +import { setupRpcUrlAndGetNetworks } from 'auctions-core/src/rpc'; import { getAllAuctions, getNewAuctionsFromActiveAuctions } from './auctions'; import notify from './notify'; import participate, { setupKeeper } from './keeper'; -import { ETHEREUM_NETWORK } from './variables'; +import { RPC_URL } from './variables'; import { setupTwitter } from './twitter'; import { setupWhitelist } from './whitelist'; import { executePreAuthorizationsIfRequested } from './authorisation'; @@ -12,29 +12,32 @@ const DEFAULT_REFETCH_INTERVAL = 60 * 1000; const SETUP_DELAY = 3 * 1000; const REFETCH_INTERVAL = parseInt(process.env.REFETCH_INTERVAL ?? '') || DEFAULT_REFETCH_INTERVAL; -const loop = async function (): Promise { +const loop = async function (network: string): Promise { try { - const activeAuctions = await getAllAuctions(ETHEREUM_NETWORK); + const activeAuctions = await getAllAuctions(network); if (activeAuctions.length === 0) { return; } const newAuctions = getNewAuctionsFromActiveAuctions(activeAuctions); newAuctions.map(notify); - participate(activeAuctions); + participate(network, activeAuctions); } catch (error) { console.error('loop error:', error); } }; const start = async function (): Promise { + if (!RPC_URL) { + throw new Error('Required `RPC_URL` env variable was not provided, please refer to the readme'); + } await delay(SETUP_DELAY); - getNetworkConfigByType(ETHEREUM_NETWORK); + const { defaultNetwork: network } = await setupRpcUrlAndGetNetworks(RPC_URL); setupWhitelist(); await setupTwitter(); - await setupKeeper(); - await executePreAuthorizationsIfRequested(); - loop(); - setInterval(loop, REFETCH_INTERVAL); + await setupKeeper(network); + await executePreAuthorizationsIfRequested(network); + loop(network); + setInterval(() => loop(network), REFETCH_INTERVAL); }; start().catch(error => { diff --git a/bot/src/keeper.ts b/bot/src/keeper.ts index 8ec8a1d66..ea518cd18 100644 --- a/bot/src/keeper.ts +++ b/bot/src/keeper.ts @@ -1,13 +1,13 @@ import { AuctionInitialInfo } from 'auctions-core/src/types'; import getSigner, { createSigner, setSigner } from 'auctions-core/src/signer'; import { bidWithCallee, enrichAuction } from 'auctions-core/src/auctions'; -import { ETHEREUM_NETWORK, KEEPER_MINIMUM_NET_PROFIT_DAI, KEEPER_WALLET_PRIVATE_KEY } from './variables'; +import { KEEPER_MINIMUM_NET_PROFIT_DAI, KEEPER_WALLET_PRIVATE_KEY } from './variables'; import { checkAndAuthorizeCollateral, checkAndAuthorizeWallet } from './authorisation'; let isSetupCompleted = false; const currentlyExecutedAuctions = new Set(); -export const setupKeeper = async function () { +export const setupKeeper = async function (network: string) { if (!KEEPER_WALLET_PRIVATE_KEY) { console.warn('keeper: KEEPER_WALLET_PRIVATE_KEY variable is not set, keeper will not run'); return; @@ -17,8 +17,8 @@ export const setupKeeper = async function () { return; } try { - setSigner(ETHEREUM_NETWORK, createSigner(ETHEREUM_NETWORK, KEEPER_WALLET_PRIVATE_KEY)); - const signer = await getSigner(ETHEREUM_NETWORK); + setSigner(network, createSigner(network, KEEPER_WALLET_PRIVATE_KEY)); + const signer = await getSigner(network); const address = await signer.getAddress(); isSetupCompleted = true; console.info( @@ -29,16 +29,16 @@ export const setupKeeper = async function () { } }; -const checkAndParticipateIfPossible = async function (auction: AuctionInitialInfo) { +const checkAndParticipateIfPossible = async function (network: string, auction: AuctionInitialInfo) { // check if setupKeeper hasn't run if (!isSetupCompleted) { return; } - const signer = await getSigner(ETHEREUM_NETWORK); + const signer = await getSigner(network); // enrich the auction with more numbers - const auctionTransaction = await enrichAuction(ETHEREUM_NETWORK, auction); + const auctionTransaction = await enrichAuction(network, auction); // check if auction became inactive or finished if (auctionTransaction.isFinished) { @@ -85,31 +85,32 @@ const checkAndParticipateIfPossible = async function (auction: AuctionInitialInf const walletAddress = await signer.getAddress(); // try to authorize the wallet then return - const wasWalletNewlyAuthorized = await checkAndAuthorizeWallet(walletAddress); + const wasWalletNewlyAuthorized = await checkAndAuthorizeWallet(network, walletAddress); if (wasWalletNewlyAuthorized) { // restart auction evaluation in case authorization was executed - await checkAndParticipateIfPossible(auction); + await checkAndParticipateIfPossible(network, auction); return; } // check the collateral authorization status and authorize if needed const wasCollateralNewlyAuthorized = await checkAndAuthorizeCollateral( + network, walletAddress, auctionTransaction.collateralType ); if (wasCollateralNewlyAuthorized) { // restart auction evaluation in case authorization was executed - await checkAndParticipateIfPossible(auction); + await checkAndParticipateIfPossible(network, auction); return; } // bid on the Auction console.info(`keeper: auction "${auctionTransaction.id}": attempting swap execution`); - const bidHash = await bidWithCallee(ETHEREUM_NETWORK, auctionTransaction, walletAddress); + const bidHash = await bidWithCallee(network, auctionTransaction, walletAddress); console.info(`keeper: auction "${auctionTransaction.id}" was succesfully executed via "${bidHash}" transaction`); }; -const participateInAuction = async function (auction: AuctionInitialInfo) { +const participateInAuction = async function (network: string, auction: AuctionInitialInfo) { // check if this auction is currently executed to avoid double execution if (currentlyExecutedAuctions.has(auction.id)) { return; @@ -118,7 +119,7 @@ const participateInAuction = async function (auction: AuctionInitialInfo) { // execute try { - await checkAndParticipateIfPossible(auction); + await checkAndParticipateIfPossible(network, auction); } catch (error) { console.error(`keeper: unexpected error: ${(error instanceof Error && error.message) || 'unknown'}`); } @@ -127,9 +128,9 @@ const participateInAuction = async function (auction: AuctionInitialInfo) { currentlyExecutedAuctions.delete(auction.id); }; -export const participate = async function (auctions: AuctionInitialInfo[]) { +export const participate = async function (network: string, auctions: AuctionInitialInfo[]) { for (const auction of auctions) { - await participateInAuction(auction); + await participateInAuction(network, auction); } }; diff --git a/bot/src/variables.ts b/bot/src/variables.ts index 680283f96..274aee8af 100644 --- a/bot/src/variables.ts +++ b/bot/src/variables.ts @@ -1,4 +1,4 @@ -export const ETHEREUM_NETWORK = process.env.ETHEREUM_NETWORK || 'kovan'; +export const RPC_URL = process.env.RPC_URL; export const KEEPER_MINIMUM_NET_PROFIT_DAI = parseInt(process.env.KEEPER_MINIMUM_NET_PROFIT_DAI || ''); export const KEEPER_WALLET_PRIVATE_KEY = process.env.KEEPER_WALLET_PRIVATE_KEY; export const WHITELISTED_COLLATERALS = process.env.WHITELISTED_COLLATERALS; diff --git a/bot/src/whitelist.ts b/bot/src/whitelist.ts index 5898564f7..f7d29bca6 100644 --- a/bot/src/whitelist.ts +++ b/bot/src/whitelist.ts @@ -1,6 +1,6 @@ import { getCollateralConfigByType } from 'auctions-core/src/constants/COLLATERALS'; -import { getSupportedCollateralTypes } from 'auctions-core/dist/src/addresses'; -import { ETHEREUM_NETWORK, WHITELISTED_COLLATERALS } from './variables'; +import { getSupportedCollateralTypes } from 'auctions-core/src/addresses'; +import { WHITELISTED_COLLATERALS } from './variables'; const validateWhitelist = function (whitelist: string[]) { const unsupportedCollateralTypes: string[] = []; @@ -22,11 +22,11 @@ const parseCollateralWhitelist = function (whitelist: string): string[] { return whitelist.split(',').map(item => item.trim()); }; -export const getWhitelistedCollaterals = async function () { +export const getWhitelistedCollaterals = async function (network: string) { if (WHITELISTED_COLLATERALS) { return parseCollateralWhitelist(WHITELISTED_COLLATERALS); } - return await getSupportedCollateralTypes(ETHEREUM_NETWORK); + return await getSupportedCollateralTypes(network); }; export const setupWhitelist = function () { diff --git a/core/src/addresses.ts b/core/src/addresses.ts index e95a95ff9..8ec353f9b 100644 --- a/core/src/addresses.ts +++ b/core/src/addresses.ts @@ -4,8 +4,8 @@ import memoizee from 'memoizee'; import COLLATERALS, { getAllCollateralTypes } from './constants/COLLATERALS'; import getProvider from './provider'; import CHAINLOG from './abis/CHAINLOG.json'; -import { CHAINLOG_ADDRESS } from './constants/NETWORKS'; +const CHAINLOG_ADDRESS = '0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F'; const CHAINLOG_CACHE = 24 * 60 * 60 * 1000; const getChainLogContract = async function (network: string): Promise { diff --git a/core/src/calleeFunctions/helpers/uniswapV2.ts b/core/src/calleeFunctions/helpers/uniswapV2.ts index 0e1caf7a4..b288b18e1 100644 --- a/core/src/calleeFunctions/helpers/uniswapV2.ts +++ b/core/src/calleeFunctions/helpers/uniswapV2.ts @@ -10,7 +10,7 @@ import memoizee from 'memoizee'; import { Fetcher, Token, Pair, Route, TokenAmount, Trade, TradeType } from '@uniswap/sdk'; import { abi as uniswapV2PairABI } from '@uniswap/v2-core/build/UniswapV2Pair.json'; import BigNumber from '../../bignumber'; -import { getDecimalChainIdByNetworkType } from '../../constants/NETWORKS'; +import { getDecimalChainIdByNetworkType } from '../../network'; import getProvider from '../../provider'; import { getTokenAddressByNetworkAndSymbol, getTokenDecimalsBySymbol } from '../../tokens'; import { getCollateralConfigBySymbol } from '../../constants/COLLATERALS'; diff --git a/core/src/constants/CALLEES.ts b/core/src/constants/CALLEES.ts index 1d450163c..cea460530 100644 --- a/core/src/constants/CALLEES.ts +++ b/core/src/constants/CALLEES.ts @@ -1,6 +1,6 @@ import type { CalleeAddresses } from '../types'; import { getCollateralConfigByType } from './COLLATERALS'; -import { getNetworkConfigByType } from './NETWORKS'; +import { getNetworkConfigByType } from '../network'; const CALLEES: Record = { '0x1': { @@ -24,7 +24,7 @@ const CALLEES: Record = { export const getCalleesByNetworkType = function (network: string): CalleeAddresses { const networkConfig = getNetworkConfigByType(network); - const chainId = network === 'localhost' ? '0x1' : networkConfig.chainId; + const chainId = networkConfig.isFork ? '0x1' : networkConfig.chainId; const networkCallees = CALLEES[chainId]; if (!networkCallees) { throw new Error(`Can not find callee addresses for the "${network}" network`); diff --git a/core/src/constants/NETWORKS.ts b/core/src/constants/NETWORKS.ts deleted file mode 100644 index 4f99c752b..000000000 --- a/core/src/constants/NETWORKS.ts +++ /dev/null @@ -1,90 +0,0 @@ -import type { NetworkConfig } from '../types'; - -export const CHAINLOG_ADDRESS = '0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F'; - -const NETWORKS: Record = { - mainnet: { - chainId: '0x1', - title: 'Main', - url: `https://mainnet.infura.io/v3/${process.env.INFURA_PROJECT_ID}`, - etherscanUrl: 'https://etherscan.io', - isFork: false, - }, - kovan: { - chainId: '0x2a', - title: 'Kovan', - gasPrice: 2000000000, - url: `https://kovan.infura.io/v3/${process.env.INFURA_PROJECT_ID}`, - etherscanUrl: 'https://kovan.etherscan.io', - isFork: false, - }, - goerli: { - chainId: '0x5', - title: 'Goerli', - gasPrice: 2000000000, - url: `https://goerli.infura.io/v3/${process.env.INFURA_PROJECT_ID}`, - etherscanUrl: 'https://goerli.etherscan.io', - isFork: false, - }, - localhost: { - chainId: '0x539', - title: 'Localhost:8545', - url: `http://127.0.0.1:8545`, - etherscanUrl: '', - isFork: true, - }, -}; - -const NETWORK_TITLES: Record = { - // full list can be found on https://chainlist.org - '0x1': 'mainnet', - '0x2a': 'kovan', - '0x3': 'ropsten', - '0x4': 'rinkeby', - '0x5': 'goerli', - '0x539': 'localhost', -}; - -export const getDecimalChainIdByNetworkType = function (networkType: string): number { - const network = NETWORKS[networkType]; - if (!network || !network.chainId) { - throw new Error(`No network with name "${networkType}" can be found`); - } - return parseInt(network.chainId, 16); -}; - -export const getChainIdByNetworkType = function (networkType: string | undefined): string | undefined { - if (!networkType) { - return undefined; - } - const network = NETWORKS[networkType]; - return network && network.chainId; -}; - -export const getNetworkTypeByChainId = function (chainId: string | undefined): string | undefined { - const networkEntry = Object.entries(NETWORKS).find(([_, networkObject]) => networkObject.chainId === chainId); - return networkEntry && networkEntry[0]; -}; - -export const getNetworkConfigByType = function (networkType: string | undefined): NetworkConfig { - if (!networkType || !NETWORKS[networkType]) { - throw new Error(`No network found with name "${networkType}"`); - } - return NETWORKS[networkType]; -}; - -export const getNetworkTitleByChainId = function (chainId: string | undefined) { - if (!chainId) { - return; - } - return NETWORK_TITLES[chainId]; -}; - -export const getNetworks = function (isDev: boolean): Record { - if (isDev) { - return NETWORKS; - } - //eslint-disable-next-line @typescript-eslint/no-unused-vars - const { localhost, ...otherNetworks } = NETWORKS; - return otherNetworks; -}; diff --git a/core/src/date.ts b/core/src/date.ts index 01c8fa2a6..64fbb4061 100644 --- a/core/src/date.ts +++ b/core/src/date.ts @@ -1,6 +1,6 @@ import memoizee from 'memoizee'; import getProvider from './provider'; -import { getNetworkConfigByType } from './constants/NETWORKS'; +import { getNetworkConfigByType } from './network'; const CURRENT_BLOCK_DATE_CACHE_EXPIRY_MS = 60 * 1000; diff --git a/core/src/execute.ts b/core/src/execute.ts index c39233519..2cdc60f07 100644 --- a/core/src/execute.ts +++ b/core/src/execute.ts @@ -3,7 +3,7 @@ import memoizee from 'memoizee'; import getContract from './contracts'; import trackTransaction from './tracker'; import { getGasParametersForTransaction } from './gas'; -import { getNetworkConfigByType } from './constants/NETWORKS'; +import { getNetworkConfigByType } from './network'; const canTransactionBeConfirmed = function (network: string, confirmTransaction?: boolean) { const networkConfig = getNetworkConfigByType(network); diff --git a/core/src/gas.ts b/core/src/gas.ts index e76c766cb..49e9179a1 100644 --- a/core/src/gas.ts +++ b/core/src/gas.ts @@ -1,7 +1,7 @@ import type { GasParameters } from './types'; import memoizee from 'memoizee'; import BigNumber from './bignumber'; -import { getNetworkConfigByType } from './constants/NETWORKS'; +import { getNetworkConfigByType } from './network'; import { ETH_NUMBER_OF_DIGITS } from './constants/UNITS'; import getProvider from './provider'; diff --git a/core/src/network.ts b/core/src/network.ts new file mode 100644 index 000000000..401a32901 --- /dev/null +++ b/core/src/network.ts @@ -0,0 +1,118 @@ +import type { NetworkConfig } from './types'; + +const networks: Record = {}; + +const SUPPORTED_NETWORKS: NetworkConfig[] = [ + { + chainId: '0x1', + type: 'mainnet', + title: 'Main', + url: '', + etherscanUrl: 'https://etherscan.io', + isFork: false, + }, + { + chainId: '0x2a', + type: 'kovan', + title: 'Kovan', + gasPrice: 2000000000, + url: '', + etherscanUrl: 'https://kovan.etherscan.io', + isFork: false, + }, + { + chainId: '0x5', + type: 'goerli', + title: 'Goerli', + gasPrice: 2000000000, + url: '', + etherscanUrl: 'https://goerli.etherscan.io', + isFork: false, + }, +]; + +export const getDefaultNetworkConfigs = function (infuraProjectId: string, isDev?: boolean): NetworkConfig[] { + const infuraNetworksWithProjectId = SUPPORTED_NETWORKS.map(network => ({ + ...network, + url: `https://${network.type}.infura.io/v3/${infuraProjectId}`, + })); + if (!isDev) { + return infuraNetworksWithProjectId; + } + return [ + ...infuraNetworksWithProjectId, + { + chainId: '0x539', + type: 'localhost', + title: 'Localhost:8545', + url: `http://127.0.0.1:8545`, + etherscanUrl: '', + isFork: true, + }, + ]; +}; + +export const getCustomNetworkConfig = function (rpcUrl: string, chainId: string): NetworkConfig { + const matchingNetwork = SUPPORTED_NETWORKS.find(n => n.chainId === chainId); + if (matchingNetwork) { + return { + ...matchingNetwork, + title: `${matchingNetwork.title}-like`, + url: rpcUrl, + }; + } + return { + chainId, + type: 'custom', + title: `Chain ${chainId}`, + url: rpcUrl, + etherscanUrl: '', + isFork: true, + }; +}; + +export const getNetworks = function (): NetworkConfig[] { + return Object.values(networks); +}; + +export const setNetwork = function (networkConfig: NetworkConfig): void { + networks[networkConfig.type] = networkConfig; +}; + +const NETWORK_TITLES: Record = { + // full list can be found on https://chainlist.org + '0x1': 'mainnet', + '0x2a': 'kovan', + '0x5': 'goerli', + '0x539': 'custom', +}; + +export const getDecimalChainIdByNetworkType = function (networkType: string): number { + const network = networks[networkType]; + if (!network || !network.chainId) { + throw new Error(`No network with name "${networkType}" can be found`); + } + return parseInt(network.chainId, 16); +}; + +export const getChainIdByNetworkType = function (networkType: string | undefined): string | undefined { + if (!networkType) { + return undefined; + } + const network = networks[networkType]; + return network && network.chainId; +}; + +export const getNetworkTypeByChainId = function (chainId: string | undefined): string | undefined { + if (!chainId) { + return; + } + return NETWORK_TITLES[chainId]; +}; + +export const getNetworkConfigByType = function (networkType: string | undefined): NetworkConfig { + if (!networkType || !networks[networkType]) { + throw new Error(`No network found with name "${networkType}"`); + } + return networks[networkType]; +}; diff --git a/core/src/provider.ts b/core/src/provider.ts index eef4947bb..95c8e84d8 100644 --- a/core/src/provider.ts +++ b/core/src/provider.ts @@ -1,5 +1,5 @@ import { ethers } from 'ethers'; -import { getNetworkConfigByType } from './constants/NETWORKS'; +import { getNetworkConfigByType } from './network'; const providers: Record> = {}; diff --git a/core/src/rpc.ts b/core/src/rpc.ts new file mode 100644 index 000000000..3bbbe3d06 --- /dev/null +++ b/core/src/rpc.ts @@ -0,0 +1,44 @@ +import { ethers } from 'ethers'; +import type { NetworkConfig } from './types'; +import { + getDefaultNetworkConfigs, + getCustomNetworkConfig, + getNetworks, + setNetwork, + getNetworkTypeByChainId, +} from './network'; + +const getChainIdFromRpcUrl = async function (rpcUrl: string): Promise { + const provider = new ethers.providers.StaticJsonRpcProvider({ url: rpcUrl }); + const networkInfo = await provider.getNetwork(); + if (!networkInfo || !networkInfo.chainId) { + throw new Error(`Can not verify RPC url`); + } + return `0x${networkInfo.chainId.toString(16)}`; +}; + +const parseInfuraProjectIdFromRpcUrl = function (rpcUrl: string): string | undefined { + const result = /infura\.io\/v3\/(\S+)/gm.exec(rpcUrl); + return result?.[1] ?? undefined; +}; + +export const setupRpcUrlAndGetNetworks = async function ( + rpcUrl?: string, + isDev?: boolean +): Promise<{ networks: NetworkConfig[]; defaultNetwork: string; defaultChainId: string }> { + if (!rpcUrl) { + throw new Error(`No RPC_URL env variable was provided`); + } + const chainId = await getChainIdFromRpcUrl(rpcUrl); + const defaultNetwork = getNetworkTypeByChainId(chainId); + const projectId = parseInfuraProjectIdFromRpcUrl(rpcUrl); + if (projectId && defaultNetwork) { + const networkConfigs = getDefaultNetworkConfigs(projectId, isDev); + networkConfigs.map(setNetwork); + return { networks: getNetworks(), defaultNetwork, defaultChainId: chainId }; + } else { + const networkConfig = getCustomNetworkConfig(rpcUrl, chainId); + setNetwork(networkConfig); + return { networks: getNetworks(), defaultNetwork: networkConfig.type, defaultChainId: chainId }; + } +}; diff --git a/core/src/signer.ts b/core/src/signer.ts index e4926c290..9babc18f0 100644 --- a/core/src/signer.ts +++ b/core/src/signer.ts @@ -1,5 +1,5 @@ import { ethers } from 'ethers'; -import { getNetworkConfigByType } from './constants/NETWORKS'; +import { getNetworkConfigByType } from './network'; import { createProvider } from './provider'; const signers: Record> = {}; diff --git a/core/src/time.ts b/core/src/time.ts index 64f911995..146480975 100644 --- a/core/src/time.ts +++ b/core/src/time.ts @@ -1,6 +1,6 @@ import memoizee from 'memoizee'; import getProvider from './provider'; -import { getNetworkConfigByType } from './constants/NETWORKS'; +import { getNetworkConfigByType } from './network'; const CURRENT_BLOCK_DATE_CACHE_EXPIRY_MS = 60 * 1000; diff --git a/core/src/types.ts b/core/src/types.ts index 474695c52..a5d572f9b 100644 --- a/core/src/types.ts +++ b/core/src/types.ts @@ -93,6 +93,7 @@ export declare interface CollateralConfig { export declare interface NetworkConfig { chainId: string; + type: string; title: string; url: string; gasPrice?: number; diff --git a/docker-compose.yaml b/docker-compose.yaml index 1d7500ca5..a9441c008 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -21,8 +21,8 @@ services: build: context: . dockerfile: ./frontend/Dockerfile - environment: - - INFURA_PROJECT_ID + args: + - RPC_URL - DEMO_MODE - PRODUCTION_DOMAIN - CONTACT_EMAIL diff --git a/frontend/Dockerfile b/frontend/Dockerfile index bbf8aca35..75b8e2907 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -36,16 +36,14 @@ CMD ["npm", "run", "dev"] FROM dependency-base AS production-base # bake env variables -ARG INFURA_PROJECT_ID -ENV INFURA_PROJECT_ID=${INFURA_PROJECT_ID} +ARG RPC_URL +ENV RPC_URL=${RPC_URL} ARG PRODUCTION_DOMAIN ENV PRODUCTION_DOMAIN=${PRODUCTION_DOMAIN} ARG CONTACT_EMAIL ENV CONTACT_EMAIL=${CONTACT_EMAIL} ARG STAGING_BANNER_URL ENV STAGING_BANNER_URL=${STAGING_BANNER_URL} -ARG DEFAULT_ETHEREUM_NETWORK -ENV DEFAULT_ETHEREUM_NETWORK=${DEFAULT_ETHEREUM_NETWORK} ARG HEAPIO_ID ENV HEAPIO_ID=${HEAPIO_ID} diff --git a/frontend/README.md b/frontend/README.md index 13c89c0cf..e3c395123 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -34,11 +34,11 @@ Help on both things is given in the linked resources above. ### Environment variables -- `INFURA_PROJECT_ID`: (required) [infura](https://infura.io/) project id (can be found in: dashboard -> etherium -> create new project -> settings -> keys) +- `RPC_URL`: (required) Etherium RPC url used for fetching data from the blockchain + - In case [infura](https://infura.io/) url is used, we automatically add list of default networks - `DEMO_MODE`: (optional) When set to true the page will only show a "Coming soon" screen. Can be used for production while the page is not ready yet. - `PRODUCTION_DOMAIN`: (optional) Required in order to enable [plausible.io statistics](https://github.com/moritzsternemann/vue-plausible#configuration). In addition to adding it here, the domain (e.g. `auctions.makerdao.network`) should also be registered within [plausible dashboard](https://plausible.io/). - `CONTACT_EMAIL`: (optional) Required in order to display contact link in the footer. This email should be able to accept and manage bug reports and other contact requests. - `STAGING_BANNER_URL`: (optional) When set a banner will be displayed, warning the user that they are using a staging version. The text will use `STAGING_BANNER_URL` as a link to production UI. -- `DEFAULT_ETHEREUM_NETWORK`: (optional, default `mainnet`) Can be set to change the default ethereum network - `MAX_PRIORITY_FEE_PER_GAS_WEI`: (optional, default can be found in core/src/gas.ts) – [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) `max_priority_fee_per_gas` value - `HEAPIO_ID`: (optional) [HeapIO analytics](https://heapanalytics.com/) Project's Environment ID. Required for tracking analytics. diff --git a/frontend/components/AuctionEventsBlock.vue b/frontend/components/AuctionEventsBlock.vue index 44adbe70c..86ef89e1e 100644 --- a/frontend/components/AuctionEventsBlock.vue +++ b/frontend/components/AuctionEventsBlock.vue @@ -15,7 +15,7 @@ diff --git a/frontend/components/modals/ChangeWalletNetworkModal.vue b/frontend/components/modals/ChangeWalletNetworkModal.vue index 59f9fb5b2..2e046f6cc 100644 --- a/frontend/components/modals/ChangeWalletNetworkModal.vue +++ b/frontend/components/modals/ChangeWalletNetworkModal.vue @@ -32,8 +32,8 @@ diff --git a/frontend/components/panels/WalletDaiDepositCheckPanel.vue b/frontend/components/panels/WalletDaiDepositCheckPanel.vue index ac2a8c797..7a9b4a183 100644 --- a/frontend/components/panels/WalletDaiDepositCheckPanel.vue +++ b/frontend/components/panels/WalletDaiDepositCheckPanel.vue @@ -27,7 +27,6 @@ diff --git a/frontend/components/wallet/WalletDepositWithdrawBlock.vue b/frontend/components/wallet/WalletDepositWithdrawBlock.vue index 25e49273a..1d61311b9 100644 --- a/frontend/components/wallet/WalletDepositWithdrawBlock.vue +++ b/frontend/components/wallet/WalletDepositWithdrawBlock.vue @@ -84,7 +84,6 @@ import Vue from 'vue'; import { Radio } from 'ant-design-vue'; import BigNumber from 'bignumber.js'; -import { getNetworkConfigByType } from 'auctions-core/src/constants/NETWORKS'; import TextBlock from '~/components/common/TextBlock.vue'; import BaseButton from '~/components/common/BaseButton.vue'; import BaseValueInput from '~/components/common/BaseValueInput.vue'; @@ -214,11 +213,7 @@ export default Vue.extend({ return false; }, networkTitle(): string { - try { - return getNetworkConfigByType(this.network).title; - } catch { - return this.network; - } + return this.network; }, }, methods: { diff --git a/frontend/components/wallet/WalletModal.vue b/frontend/components/wallet/WalletModal.vue index 27aaf840f..5b20fc308 100644 --- a/frontend/components/wallet/WalletModal.vue +++ b/frontend/components/wallet/WalletModal.vue @@ -55,7 +55,7 @@