feat(#507): Node Discovery

* feat: add network-switcher lib

* feat: add env variables for some deployed app urls

* feat: add network processing to environment hoook

* refactor: network handling

* refactor: remove dialog from provider and add env setter

* feat: add network switcher dialog to the trading app

* refactor: add network redirect to dialog connect callback

* fix: lint

* feat: add network configuration files to static app

* feat: update environments to use config file instead of static node url

* refactor: split out network switcher utils

* refactor: split up environment hook

* fix: jsonify env variable for possible networks

* fix: add formatter file

* feat: add network loader component

* feat: add network loader to the trading app

* fix: assign correct global state to network swicther

* feat: add status modal

* feat: add network-switcher lib

* feat: add env variables for some deployed app urls

* feat: add network processing to environment hoook

* refactor: network handling

* refactor: remove dialog from provider and add env setter

* feat: add network switcher dialog to the trading app

* refactor: add network redirect to dialog connect callback

* fix: lint

* fix: jsonify env variable for possible networks

* fix: add formatter file

* fix: assign correct global state to network swicther

* fix: failing tests from UI changes

* feat: add environment validation

* feat: add runtime validation for network configs

* fix: readd node urls to envs to avoid breaking the apps for now

* chore: rename network swicther lib to environmnet

* fix: lint

* feat: add tests for config hook

* feat: add environment hook tests

* fix: lint

* fix: lint

* feat: add environment hook tests

* feat: add storage tests

* fix: formet

* feat: improve loading states

* fix: format

* fix: use router instead of window location

* fix: rearrange network loader props and components

* fix: remove FC type

* fix: env validation

* fix: untangle returns in network loader

* fix: add teardown for env and localstorage

* fix: add custom to env networks

* fix: lint

* fix: format

* fix: lint

* fix: remove env provider from simple trading app

* fix: remove failing promise hacks

* fix: some leftover format files

* fix: remove network switcher from tsconf

* fix: move Networks to libs/environment

* fix: add defaults for ether env vars

* feat: add tests for default ether env vars

* fix: remove chain id env var from web3 container

* fix: remove chain id from the environment

* fix: format

* fix: lint token

* fix: lint env

* fix: add comment to callout hack

* fix: lint token again

* fix: remove skip

* fix: move addresses to token app

* fix: improve schema validation errors and fix token app

* fix: lint

* fix: format

* fix: format

* fix: add network loaders to apps

* fix: format

* fix: remove logs

* fix: cypress process errors

* fix: change network loader hierarchy in token

* fix: remove stray console.log

* fix: revert test changes in simple trading app

* fix: prefix env vars with NX

Co-authored-by: Dexter Edwards <dexter.edwards93@gmail.com>

* fix: improve schema validation errors and fix token app

* fix: format

* fix: disable lint rules for catch block any types

* fix: format again

* fix: remove redundant process.platform injections

* fix: format

Co-authored-by: Joe <joe@vega.xyz>
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: Dexter Edwards <dexter.edwards93@gmail.com>
This commit is contained in:
botond 2022-06-22 00:20:53 +01:00 committed by GitHub
parent 2cf4436800
commit 69b19e4b7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
114 changed files with 1522 additions and 659 deletions

View File

@ -2,7 +2,7 @@ NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-exp
NX_TENDERMINT_URL=http://localhost:26617
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
NX_VEGA_URL=http://localhost:3028/query
NX_VEGA_ENV=LOCAL
NX_VEGA_ENV=CUSTOM
NX_VEGA_REST=http://localhost:3029
CYPRESS_VEGA_TENDERMINT_URL=http://localhost:26617

View File

@ -14,7 +14,7 @@
"screenshotsFolder": "../../dist/cypress/apps/explorer-e2e/screenshots",
"chromeWebSecurity": false,
"env": {
"environment": "local",
"environment": "CUSTOM",
"tsConfig": "tsconfig.json",
"TAGS": "not @todo and not @ignore and not @manual"
}

View File

@ -2,7 +2,7 @@ NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-exp
NX_TENDERMINT_URL=http://localhost:26617
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
NX_VEGA_URL=http://localhost:3028/query
NX_VEGA_ENV=LOCAL
NX_VEGA_ENV=CUSTOM
NX_VEGA_REST=http://localhost:3029
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api

View File

@ -4,7 +4,7 @@ NX_TENDERMINT_URL=http://localhost:26617
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
NX_VEGA_URL=http://localhost:3028/query
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
NX_VEGA_ENV=LOCAL
NX_VEGA_ENV=CUSTOM
NX_VEGA_REST=http://localhost:3029
# App flags

View File

@ -2,6 +2,7 @@
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
NX_TENDERMINT_URL=https://n04.d.vega.xyz/tm
NX_TENDERMINT_WEBSOCKET_URL=wss://n04.d.vega.xyz/tm/websocket
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/devnet-network.json
NX_VEGA_URL=https://n04.d.vega.xyz/query
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
NX_VEGA_ENV=DEVNET

View File

@ -2,6 +2,7 @@
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
NX_TENDERMINT_URL=https://mainnet-observer-proxy01.ops.vega.xyz/
NX_TENDERMINT_WEBSOCKET_URL=wss://mainnet-observer-proxy01.ops.vega.xyz/websocket
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mainnet-network.json
NX_VEGA_URL=https://api.token.vega.xyz/query
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
NX_VEGA_ENV=MAINNET

View File

@ -2,6 +2,7 @@
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
NX_TENDERMINT_URL=https://n03.s.vega.xyz/tm
NX_TENDERMINT_WEBSOCKET_URL=wss://n03.s.vega.xyz/tm/websocket
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet1-network.json
NX_VEGA_URL=https://n03.s.vega.xyz/query
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
NX_VEGA_ENV=STAGNET

View File

@ -2,6 +2,7 @@
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
NX_TENDERMINT_URL=https://n03.stagnet2.vega.xyz/tm
NX_TENDERMINT_WEBSOCKET_URL=wss://n03.stagnet2.vega.xyz/tm/websocket
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet2-network.json
NX_VEGA_URL=https://n03.stagnet2.vega.xyz/query
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
NX_VEGA_ENV=STAGNET2

View File

@ -2,6 +2,7 @@
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
NX_TENDERMINT_URL=https://lb.testnet.vega.xyz/tm
NX_TENDERMINT_WEBSOCKET_URL=wss://lb.testnet.vega.xyz/tm/websocket
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json
NX_VEGA_URL=https://lb.testnet.vega.xyz/query
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
NX_VEGA_ENV=TESTNET

View File

@ -1,13 +1,11 @@
import { useState, useEffect, useMemo } from 'react';
import { useState, useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { ApolloProvider } from '@apollo/client';
import { ThemeContext, useThemeSwitcher } from '@vegaprotocol/react-helpers';
import { EnvironmentProvider } from '@vegaprotocol/network-switcher';
import { EnvironmentProvider, NetworkLoader } from '@vegaprotocol/environment';
import { createClient } from './lib/apollo-client';
import { Nav } from './components/nav';
import { Header } from './components/header';
import { Main } from './components/main';
import { DATA_SOURCES } from './config';
import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-websocket-provider';
function App() {
@ -20,13 +18,11 @@ function App() {
setMenuOpen(false);
}, [location]);
const client = useMemo(() => createClient(DATA_SOURCES.dataNodeUrl), []);
return (
<EnvironmentProvider>
<ThemeContext.Provider value={theme}>
<TendermintWebsocketProvider>
<ApolloProvider client={client}>
<NetworkLoader createClient={createClient}>
<div
className={`${
menuOpen && 'h-[100vh] overflow-hidden'
@ -42,7 +38,7 @@ function App() {
<Main />
</div>
</div>
</ApolloProvider>
</NetworkLoader>
</TendermintWebsocketProvider>
</ThemeContext.Provider>
</EnvironmentProvider>

View File

@ -17,3 +17,7 @@ NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY
NX_URL=$URL
NX_DEPLOY_URL=$DEPLOY_URL
NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
NX_VEGA_CONFIG_URL="https://static.vega.xyz/assets/testnet-network.json"
NX_VEGA_ENV = 'TESTNET'
NX_VEGA_URL="https://lb.testnet.vega.xyz/query"

View File

@ -1,4 +1,5 @@
# App configuration variables
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/devnet-network.json
NX_VEGA_URL=https://n04.d.vega.xyz/query
NX_VEGA_ENV=DEVNET
NX_VEGA_REST=https://n04.d.vega.xyz/datanode/rest

View File

@ -1,4 +1,5 @@
# App configuration variables
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mainnet-network.json
NX_VEGA_URL=https://api.token.vega.xyz/query
NX_VEGA_ENV=MAINNET
NX_VEGA_REST=https://api.token.vega.xyz/

View File

@ -1,4 +1,5 @@
# App configuration variables
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet1-network.json
NX_VEGA_URL=https://n03.s.vega.xyz/query
NX_VEGA_ENV=STAGNET
NX_VEGA_REST=https://n03.s.vega.xyz/datanode/rest

View File

@ -1,4 +1,5 @@
# App configuration variables
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet2-network.json
NX_VEGA_URL=https://n03.stagnet2.vega.xyz/query
NX_VEGA_ENV=STAGNET2
NX_VEGA_REST=https://n01.stagnet2.vega.xyz/datanode/rest

View File

@ -1,4 +1,5 @@
# App configuration variables
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json
NX_VEGA_URL=https://lb.testnet.vega.xyz/query
NX_VEGA_ENV=TESTNET
NX_VEGA_REST=https://lb.testnet.vega.xyz/datanode/rest

View File

@ -1,15 +1,13 @@
import React, { useState, useMemo, useEffect } from 'react';
import { ApolloProvider } from '@apollo/client';
import { useState, useEffect } from 'react';
import { ThemeContext } from '@vegaprotocol/react-helpers';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
import { EnvironmentProvider, NetworkLoader } from '@vegaprotocol/environment';
import { createClient } from './lib/apollo-client';
import { DATA_SOURCES } from './config';
import {
VegaConnectDialog,
VegaManageDialog,
VegaWalletProvider,
} from '@vegaprotocol/wallet';
import { EnvironmentProvider } from '@vegaprotocol/network-switcher';
import { VegaWalletConnectButton } from './components/vega-wallet-connect-button';
import { ThemeSwitcher } from '@vegaprotocol/ui-toolkit';
import { Connectors } from './lib/vega-connectors';
@ -26,8 +24,6 @@ function App() {
manage: false,
});
const client = useMemo(() => createClient(DATA_SOURCES.dataNodeUrl), []);
const [menuOpen, setMenuOpen] = useState(false);
const onToggle = () => setMenuOpen(!menuOpen);
@ -40,7 +36,7 @@ function App() {
return (
<EnvironmentProvider>
<ThemeContext.Provider value={theme}>
<ApolloProvider client={client}>
<NetworkLoader createClient={createClient}>
<VegaWalletProvider>
<AppLoader>
<div className="max-h-full min-h-full dark:bg-black dark:text-white-60 bg-white text-black-60 grid grid-rows-[min-content,1fr]">
@ -82,7 +78,7 @@ function App() {
</div>
</AppLoader>
</VegaWalletProvider>
</ApolloProvider>
</NetworkLoader>
</ThemeContext.Provider>
</EnvironmentProvider>
);

View File

@ -1,5 +0,0 @@
export const DATA_SOURCES = {
dataNodeUrl: process.env['NX_VEGA_URL'] as string,
envName: process.env['NX_VEGA_ENV'] as string,
restEndpoint: process.env['NX_VEGA_REST'] as string,
};

View File

@ -0,0 +1,3 @@
{
"hosts": ["https://n04.d.vega.xyz/query"]
}

View File

@ -0,0 +1,17 @@
{
"hosts": [
"https://vega-data-graphql.chorus.one/query",
"https://vega.xprv.io/datanode/query",
"http://nala.mainnet.vega.community:3008/query",
"http://commodum.mainnet.vega.community:3008/query",
"http://lovali.mainnet.vega.community:3008/query",
"http://b-harvest.mainnet.vega.community:3008/query",
"http://staking-facilities.mainnet.vega.community:3008/query",
"http://figment.mainnet.vega.community:3008/query",
"http://nodes-guru.mainnet.vega.community:3008/query",
"http://p2p.mainnet.vega.community:3008/query",
"http://rockaway.mainnet.vega.community:3008/query",
"http://greenfield-one.mainnet.vega.community:3008/query",
"http://ryabina.mainnet.vega.community:3008/query"
]
}

View File

@ -0,0 +1,3 @@
{
"hosts": ["https://n03.s.vega.xyz/query"]
}

View File

@ -0,0 +1,3 @@
{
"hosts": ["https://n03.stagnet2.vega.xyz/query"]
}

View File

@ -0,0 +1,3 @@
{
"hosts": ["https://lb.testnet.vega.xyz/query"]
}

View File

@ -3,7 +3,6 @@ import { DATA_SOURCES } from './config';
import { Header } from './components/header';
import { StatsManager } from '@vegaprotocol/network-stats';
import { ThemeContext } from '@vegaprotocol/react-helpers';
import { EnvironmentProvider } from '@vegaprotocol/network-switcher';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
const envName = DATA_SOURCES.envName;
@ -15,7 +14,6 @@ function App() {
const [theme, toggleTheme] = useThemeSwitcher();
return (
<EnvironmentProvider>
<ThemeContext.Provider value={theme}>
<div className="w-screen min-h-screen grid pb-24 bg-white text-black-95 dark:bg-black dark:text-white-80">
<div className="layout-grid w-screen justify-self-center">
@ -29,7 +27,6 @@ function App() {
</div>
</div>
</ThemeContext.Provider>
</EnvironmentProvider>
);
}

View File

@ -21,7 +21,6 @@ REACT_APP_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
# App configuration variables
NX_VEGA_ENV=TESTNET
NX_VEGA_URL=https://lb.testnet.vega.xyz/query
NX_ETHEREUM_CHAIN_ID=3
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://ropsten.etherscan.io
NX_FAIRGROUND=false

View File

@ -1,6 +1,5 @@
# App configuration variables
NX_VEGA_ENV=DEVNET
NX_VEGA_URL=https://n04.d.vega.xyz/query
NX_ETHEREUM_CHAIN_ID=3
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://ropsten.etherscan.io

View File

@ -1,6 +1,5 @@
# App configuration variables
NX_VEGA_ENV=MAINNET
NX_VEGA_URL=https://api.token.vega.xyz/query
NX_ETHEREUM_CHAIN_ID=1
NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://etherscan.io

View File

@ -1,6 +1,5 @@
# App configuration variables
NX_VEGA_ENV=STAGNET
NX_VEGA_URL=https://n03.s.vega.xyz/query
NX_ETHEREUM_CHAIN_ID=3
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://ropsten.etherscan.io

View File

@ -1,6 +1,5 @@
# App configuration variables
NX_VEGA_ENV=STAGNET2
NX_VEGA_URL=https://n03.stagnet2.vega.xyz/query
NX_ETHEREUM_CHAIN_ID=3
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://ropsten.etherscan.io

View File

@ -1,6 +1,5 @@
# App configuration variables
NX_VEGA_ENV=TESTNET
NX_VEGA_URL=https://lb.testnet.vega.xyz/query
NX_ETHEREUM_CHAIN_ID=3
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://ropsten.etherscan.io

View File

@ -20,8 +20,8 @@ REACT_APP_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
# App configuration variables
NX_VEGA_ENV=TESTNET
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json
NX_VEGA_URL=https://lb.testnet.vega.xyz/query
NX_ETHEREUM_CHAIN_ID=3
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://ropsten.etherscan.io
NX_FAIRGROUND=false

View File

@ -1,7 +1,7 @@
# App configuration variables
NX_VEGA_ENV=DEVNET
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/devnet-network.json
NX_VEGA_URL=https://n04.d.vega.xyz/query
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET":"https://dev.token.vega.xyz","STAGNET2":"staging2.token.vega.xyz","TESTNET":"token.fairground.wtf","MAINNET":"token.vega.xyz"}'
NX_ETHEREUM_CHAIN_ID=3
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://ropsten.etherscan.io

View File

@ -1,7 +1,7 @@
# App configuration variables
NX_VEGA_ENV=MAINNET
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mainnet-network.json
NX_VEGA_URL=https://api.token.vega.xyz/query
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET":"https://dev.token.vega.xyz","STAGNET2":"staging2.token.vega.xyz","TESTNET":"token.fairground.wtf","MAINNET":"token.vega.xyz"}'
NX_ETHEREUM_CHAIN_ID=1
NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://etherscan.io

View File

@ -1,7 +1,7 @@
# App configuration variables
NX_VEGA_ENV=STAGNET
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet1-network.json
NX_VEGA_URL=https://n03.s.vega.xyz/query
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET":"https://dev.token.vega.xyz","STAGNET2":"staging2.token.vega.xyz","TESTNET":"token.fairground.wtf","MAINNET":"token.vega.xyz"}'
NX_ETHEREUM_CHAIN_ID=3
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://ropsten.etherscan.io

View File

@ -1,7 +1,7 @@
# App configuration variables
NX_VEGA_ENV=STAGNET2
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet2-network.json
NX_VEGA_URL=https://n03.stagnet2.vega.xyz/query
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET":"https://dev.token.vega.xyz","STAGNET2":"staging2.token.vega.xyz","TESTNET":"token.fairground.wtf","MAINNET":"token.vega.xyz"}'
NX_ETHEREUM_CHAIN_ID=3
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://ropsten.etherscan.io

View File

@ -1,7 +1,7 @@
# App configuration variables
NX_VEGA_ENV=TESTNET
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json
NX_VEGA_URL=https://lb.testnet.vega.xyz/query
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET":"https://dev.token.vega.xyz","STAGNET2":"staging2.token.vega.xyz","TESTNET":"token.fairground.wtf","MAINNET":"token.vega.xyz"}'
NX_ETHEREUM_CHAIN_ID=3
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://ropsten.etherscan.io

View File

@ -48,13 +48,13 @@ There are a few different configuration options offered for this app:
| `NX_APP_INFURA_ID` | Infura fallback for if the user does not have a web3 compatible browser |
| `NX_APP_HOSTED_WALLET_ENABLED` | If the hosted wallet is enabled or not. If so then allow users to login using the hosted wallet |
| `NX_APP_ENV` | Change network to connect to. When set to CUSTOM use CUSTOM\_\* vars for network parameters |
| `CUSTOM_URLS` | When NX_APP_ENV=CUSTOM use these Data Node REST URLs, optional if CUSTOM_URLS_WITH_GRAPHQL is used. |
| `CUSTOM_URLS_WITH_GRAPHQL` | When NX_APP_ENV=CUSTOM use these Data Node GraphQL URLs, optional if CUSTOM_URLS is used. |
| `CUSTOM_TOKEN_ADDRESS` | When NX_APP_ENV=CUSTOM specify Vega token address. |
| `CUSTOM_CLAIM_ADDRESS` | When NX_APP_ENV=CUSTOM specify Vega claim address. |
| `CUSTOM_LOCKED_ADDRESS` | When NX_APP_ENV=CUSTOM specify Vega locked address. |
| `CUSTOM_VESTING_ADDRESS` | When NX_APP_ENV=CUSTOM specify Vega vesting address. |
| `CUSTOM_STAKING_BRIDGE` | When NX_APP_ENV=CUSTOM specify Vega staking bridge address. |
| `NX_CUSTOM_URLS` | When NX_APP_ENV=CUSTOM use these Data Node REST URLs, optional if CUSTOM_URLS_WITH_GRAPHQL is used. |
| `NX_CUSTOM_URLS_WITH_GRAPHQL` | When NX_APP_ENV=CUSTOM use these Data Node GraphQL URLs, optional if CUSTOM_URLS is used. |
| `NX_CUSTOM_TOKEN_ADDRESS` | When NX_APP_ENV=CUSTOM specify Vega token address. |
| `NX_CUSTOM_CLAIM_ADDRESS` | When NX_APP_ENV=CUSTOM specify Vega claim address. |
| `NX_CUSTOM_LOCKED_ADDRESS` | When NX_APP_ENV=CUSTOM specify Vega locked address. |
| `NX_CUSTOM_VESTING_ADDRESS` | When NX_APP_ENV=CUSTOM specify Vega vesting address. |
| `NX_CUSTOM_STAKING_BRIDGE` | When NX_APP_ENV=CUSTOM specify Vega staking bridge address. |
## Example configs:

View File

@ -17,27 +17,31 @@ import { AppRouter } from './routes';
import { Web3Provider } from '@vegaprotocol/web3';
import { VegaWalletDialogs } from './components/vega-wallet-dialogs';
import { VegaWalletProvider } from '@vegaprotocol/wallet';
import { createConnectors } from './lib/web3-connectors';
import { ApolloProvider } from '@apollo/client';
import { createClient } from './lib/apollo-client';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { useEthereumConfig } from '@vegaprotocol/web3';
import {
EnvironmentProvider,
useEnvironment,
} from '@vegaprotocol/network-switcher';
EnvironmentProvider,
NetworkLoader,
} from '@vegaprotocol/environment';
import { createClient } from './lib/apollo-client';
import { createConnectors } from './lib/web3-connectors';
const AppContainer = () => {
const sideBar = React.useMemo(() => [<EthWallet />, <VegaWallet />], []);
const { ETHEREUM_PROVIDER_URL, ETHEREUM_CHAIN_ID, VEGA_URL } =
useEnvironment();
const Connectors = useMemo(
() => createConnectors(ETHEREUM_PROVIDER_URL, ETHEREUM_CHAIN_ID),
[ETHEREUM_CHAIN_ID, ETHEREUM_PROVIDER_URL]
);
const client = useMemo(() => createClient(VEGA_URL), [VEGA_URL]);
const { config, loading, error } = useEthereumConfig();
const { ETHEREUM_PROVIDER_URL } = useEnvironment();
const Connectors = useMemo(() => {
if (config?.chain_id) {
return createConnectors(ETHEREUM_PROVIDER_URL, Number(config.chain_id));
}
return undefined;
}, [config?.chain_id, ETHEREUM_PROVIDER_URL]);
return (
<ApolloProvider client={client}>
<Router>
<AppStateProvider>
<AsyncRenderer loading={loading} data={config} error={error}>
{Connectors && (
<Web3Provider connectors={Connectors}>
<Web3Connector>
<VegaWalletProvider>
@ -61,16 +65,19 @@ const AppContainer = () => {
</VegaWalletProvider>
</Web3Connector>
</Web3Provider>
)}
</AsyncRenderer>
</AppStateProvider>
</Router>
</ApolloProvider>
);
};
function App() {
return (
<EnvironmentProvider>
<NetworkLoader createClient={createClient}>
<AppContainer />
</NetworkLoader>
</EnvironmentProvider>
);
}

View File

@ -1,15 +1,14 @@
import { useTranslation } from 'react-i18next';
import { useEnvironment } from '@vegaprotocol/network-switcher';
import { useAddAssetSupported } from '../../hooks/use-add-asset-to-wallet';
import vegaVesting from '../../images/vega_vesting.png';
import { AddTokenButtonLink } from '../add-token-button/add-token-button';
import { Callout } from '@vegaprotocol/ui-toolkit';
import { ENV } from '../../config/env';
export const AddLockedTokenAddress = () => {
const { t } = useTranslation();
const addSupported = useAddAssetSupported();
const { ADDRESSES } = useEnvironment();
return (
<Callout
title={t(
@ -20,7 +19,7 @@ export const AddLockedTokenAddress = () => {
<>
<p className="flex justify-center">
<AddTokenButtonLink
address={ADDRESSES.lockedAddress}
address={ENV.addresses.lockedAddress}
symbol="VEGA🔒"
decimals={18}
image={vegaVesting}
@ -36,7 +35,7 @@ export const AddLockedTokenAddress = () => {
{t(
'The token address is {{address}}. Hit the add token button in your ERC20 wallet and enter this address.',
{
address: ADDRESSES.lockedAddress,
address: ENV.addresses.lockedAddress,
}
)}
</p>

View File

@ -1,7 +1,7 @@
import { Callout, Intent } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next';
import { Link } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/network-switcher';
import { useEnvironment } from '@vegaprotocol/environment';
import type { ReactElement } from 'react';
export const TransactionComplete = ({

View File

@ -2,7 +2,7 @@ import { Button, Callout, Intent } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next';
import { Link } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/network-switcher';
import { useEnvironment } from '@vegaprotocol/environment';
export interface TransactionErrorProps {
error: Error | null;

View File

@ -2,7 +2,7 @@ import React from 'react';
import { Callout, Loader } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next';
import { Link } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/network-switcher';
import { useEnvironment } from '@vegaprotocol/environment';
export const TransactionPending = ({
hash,

View File

@ -1,5 +1,5 @@
import { Dialog, Link } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/network-switcher';
import { useEnvironment } from '@vegaprotocol/environment';
import React from 'react';
import { useTranslation } from 'react-i18next';

View File

@ -1,5 +1,6 @@
import { useEnvironment } from '@vegaprotocol/network-switcher';
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/environment';
import { useEthereumConfig } from '@vegaprotocol/web3';
import { Button, Splash, AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { Web3ConnectDialog } from '@vegaprotocol/web3';
import { useWeb3React } from '@web3-react/core';
import type { ReactElement } from 'react';
@ -16,30 +17,35 @@ interface Web3ConnectorProps {
export function Web3Connector({ children }: Web3ConnectorProps) {
const { appState, appDispatch } = useAppState();
const { ETHEREUM_PROVIDER_URL, ETHEREUM_CHAIN_ID } = useEnvironment();
const Connectors = useMemo(
() => createConnectors(ETHEREUM_PROVIDER_URL, ETHEREUM_CHAIN_ID),
[ETHEREUM_CHAIN_ID, ETHEREUM_PROVIDER_URL]
);
const { ETHEREUM_PROVIDER_URL } = useEnvironment();
const { config, loading, error } = useEthereumConfig();
const Connectors = useMemo(() => {
if (config?.chain_id) {
return createConnectors(ETHEREUM_PROVIDER_URL, Number(config.chain_id));
}
return undefined;
}, [config?.chain_id, ETHEREUM_PROVIDER_URL]);
const setDialogOpen = useCallback(
(isOpen: boolean) => {
appDispatch({ type: AppStateActionType.SET_ETH_WALLET_OVERLAY, isOpen });
},
[appDispatch]
);
const appChainId = Number(ETHEREUM_CHAIN_ID);
const appChainId = Number(config?.chain_id);
return (
<>
<AsyncRenderer loading={loading} error={error} data={config}>
<Web3Content appChainId={appChainId} setDialogOpen={setDialogOpen}>
{children}
</Web3Content>
{Connectors && (
<Web3ConnectDialog
connectors={Connectors}
dialogOpen={appState.ethConnectOverlay}
setDialogOpen={setDialogOpen}
desiredChainId={appChainId}
/>
</>
)}
</AsyncRenderer>
);
}
@ -49,11 +55,7 @@ interface Web3ContentProps {
setDialogOpen: (isOpen: boolean) => void;
}
export const Web3Content = ({
children,
appChainId,
setDialogOpen,
}: Web3ContentProps) => {
export const Web3Content = ({ children, appChainId }: Web3ContentProps) => {
const { error, connector, chainId } = useWeb3React();
useEffect(() => {

View File

@ -1,3 +1,5 @@
import type { Networks } from '@vegaprotocol/environment';
const windowOrDefault = (key: string) => {
if (window._env_ && window._env_[key]) {
return window._env_[key];
@ -7,10 +9,49 @@ const windowOrDefault = (key: string) => {
const TRUTHY = ['1', 'true'];
interface VegaContracts {
claimAddress: string;
lockedAddress: string;
}
const customClaimAddress = process.env['NX_CUSTOM_CLAIM_ADDRESS'] as string;
const customLockedAddress = process.env['NX_CUSTOM_LOCKED_ADDRESS'] as string;
export const ContractAddresses: {
[key in Networks | 'CUSTOM']: VegaContracts;
} = {
CUSTOM: {
claimAddress: customClaimAddress ?? '0x0',
lockedAddress: customLockedAddress ?? '0x0',
},
DEVNET: {
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994',
lockedAddress: '0x0',
},
STAGNET: {
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
},
STAGNET2: {
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
},
TESTNET: {
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
},
MAINNET: {
claimAddress: '0x0ee1fb382caf98e86e97e51f9f42f8b4654020f3',
lockedAddress: '0x78344c7305d73a7a0ac3c94cd9960f4449a1814e',
},
};
const envName = windowOrDefault('NX_VEGA_ENV') ?? 'local';
export const ENV = {
// Environment
dsn: windowOrDefault('NX_SENTRY_DSN'),
envName: windowOrDefault('NX_VEGA_ENV'),
envName,
commit: windowOrDefault('NX_COMMIT_REF'),
branch: windowOrDefault('NX_BRANCH'),
vegaUrl: windowOrDefault('NX_VEGA_URL'),
@ -26,4 +67,6 @@ export const ENV = {
process.env['NX_IS_NEW_BRIDGE_CONTRACT'] as string
),
},
addresses:
ContractAddresses[(envName === 'local' ? 'CUSTOM' : envName) as Networks],
};

View File

@ -1,4 +1,3 @@
export * from './flags';
export * from './links';
export * from './network-params';
export * from './vega';

View File

@ -1,176 +0,0 @@
import { Networks } from '@vegaprotocol/react-helpers';
interface VegaNode {
url: string;
api: {
GraphQL: boolean;
};
}
type VegaNets = {
[N in Networks]: {
nodes: VegaNode[];
};
};
export type NetworkConfig = {
[N in Networks]: string[];
};
export const VegaNetworks: VegaNets = {
[Networks.DEVNET]: {
nodes: [
{
url: 'https://n01.d.vega.xyz',
api: {
GraphQL: false,
},
},
{
url: 'https://n02.d.vega.xyz',
api: {
GraphQL: false,
},
},
{
url: 'https://n03.d.vega.xyz',
api: {
GraphQL: false,
},
},
{
url: 'https://n04.d.vega.xyz',
api: {
GraphQL: true,
},
},
],
},
[Networks.STAGNET]: {
nodes: [
{
url: 'https://n01.s.vega.xyz',
api: {
GraphQL: false,
},
},
{
url: 'https://n02.s.vega.xyz',
api: {
GraphQL: false,
},
},
{
url: 'https://n03.s.vega.xyz',
api: {
GraphQL: true,
},
},
{
url: 'https://n04.s.vega.xyz',
api: {
GraphQL: false,
},
},
{
url: 'https://n05.s.vega.xyz',
api: {
GraphQL: false,
},
},
],
},
[Networks.STAGNET2]: {
nodes: [
{
url: 'https://n03.stagnet2.vega.xyz',
api: {
GraphQL: true,
},
},
],
},
[Networks.TESTNET]: {
nodes: [
{
url: 'https://lb.testnet.vega.xyz',
api: {
GraphQL: true,
},
},
{
url: 'https://n01.testnet.vega.xyz',
api: {
GraphQL: false,
},
},
{
url: 'https://n02.testnet.vega.xyz',
api: {
GraphQL: false,
},
},
{
url: 'https://n03.testnet.vega.xyz',
api: {
GraphQL: false,
},
},
{
url: 'https://n04.testnet.vega.xyz',
api: {
GraphQL: false,
},
},
{
url: 'https://n05.testnet.vega.xyz',
api: {
GraphQL: false,
},
},
{
url: 'https://n06.testnet.vega.xyz',
api: {
GraphQL: true,
},
},
{
url: 'https://n07.testnet.vega.xyz',
api: {
GraphQL: true,
},
},
{
url: 'https://n08.testnet.vega.xyz',
api: {
GraphQL: true,
},
},
{
url: 'https://n09.testnet.vega.xyz',
api: {
GraphQL: true,
},
},
],
},
[Networks.MAINNET]: {
nodes: [],
},
};
export const GraphQLNodes = Object.keys(VegaNetworks).reduce(
(obj: Record<string, string[]>, network) => {
const rawNodes: VegaNode[] = VegaNetworks[network as Networks].nodes;
const nodesWithGraphQL = rawNodes
.filter((n) => n.api.GraphQL)
.map((n) => n.url);
obj[network] = nodesWithGraphQL;
return obj;
},
{}
);
export const EnvironmentNodes = GraphQLNodes[
process.env['NX_VEGA_ENV'] as Networks
] as string[];

View File

@ -6,14 +6,15 @@ import {
} from '@vegaprotocol/smart-contracts';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useWeb3React } from '@web3-react/core';
import React, { useMemo } from 'react';
import React from 'react';
import { SplashLoader } from '../../components/splash-loader';
import type { ContractsContextShape } from './contracts-context';
import { ContractsContext } from './contracts-context';
import { createDefaultProvider } from '../../lib/web3-connectors';
import { useEthereumConfig } from '@vegaprotocol/web3';
import { useEnvironment } from '@vegaprotocol/network-switcher';
import { useEnvironment } from '@vegaprotocol/environment';
import { ENV } from '../../config/env';
/**
* Provides Vega Ethereum contract instances to its children.
@ -21,14 +22,9 @@ import { useEnvironment } from '@vegaprotocol/network-switcher';
export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
const { provider: activeProvider, account } = useWeb3React();
const { config } = useEthereumConfig();
const { VEGA_ENV, ADDRESSES, ETHEREUM_PROVIDER_URL, ETHEREUM_CHAIN_ID } =
useEnvironment();
const { VEGA_ENV, ETHEREUM_PROVIDER_URL } = useEnvironment();
const [contracts, setContracts] =
React.useState<ContractsContextShape | null>(null);
const defaultProvider = useMemo(
() => createDefaultProvider(ETHEREUM_PROVIDER_URL, ETHEREUM_CHAIN_ID),
[ETHEREUM_PROVIDER_URL, ETHEREUM_CHAIN_ID]
);
// Create instances of contract classes. If we have an account use a signer for the
// contracts so that we can sign transactions, otherwise use the provider for just
@ -37,6 +33,12 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
const run = async () => {
let signer = null;
if (config) {
const defaultProvider = createDefaultProvider(
ETHEREUM_PROVIDER_URL,
Number(config.chain_id)
);
const provider = activeProvider ? activeProvider : defaultProvider;
if (
@ -64,12 +66,13 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
config.token_vesting_contract.address,
signer || provider
),
claim: new Claim(ADDRESSES.claimAddress, signer || provider),
claim: new Claim(ENV.addresses.claimAddress, signer || provider),
});
}
}
};
run();
}, [activeProvider, account, config, ADDRESSES, VEGA_ENV, defaultProvider]);
}, [activeProvider, account, config, VEGA_ENV, ETHEREUM_PROVIDER_URL]);
if (!contracts) {
return (

View File

@ -1,9 +1,9 @@
import React from 'react';
import * as Sentry from '@sentry/react';
import { Networks } from '@vegaprotocol/react-helpers';
import { Networks } from '@vegaprotocol/environment';
import { useWeb3React } from '@web3-react/core';
import { MetaMask } from '@web3-react/metamask';
import { useEnvironment } from '@vegaprotocol/network-switcher';
import { useEnvironment } from '@vegaprotocol/environment';
export const useAddAssetSupported = () => {
const { connector } = useWeb3React();

View File

@ -1,8 +1,8 @@
import type { Networks } from '@vegaprotocol/react-helpers';
import { useFetch } from '@vegaprotocol/react-helpers';
import type { Tranche } from '@vegaprotocol/smart-contracts';
import React, { useEffect } from 'react';
import { useEnvironment } from '@vegaprotocol/network-switcher';
import type { Networks } from '@vegaprotocol/environment';
import { useEnvironment } from '@vegaprotocol/environment';
import { BigNumber } from '../lib/bignumber';
@ -12,6 +12,7 @@ const TRANCHES_URLS: { [N in Networks]: string } = {
STAGNET: 'https://static.vega.xyz/assets/stagnet1-tranches.json',
STAGNET2: 'https://static.vega.xyz/assets/stagnet2-tranches.json',
DEVNET: 'https://static.vega.xyz/assets/devnet-tranches.json',
CUSTOM: 'https://static.vega.xyz/assets/testnet-tranches.json',
};
export function useTranches() {

View File

@ -1,5 +1,5 @@
import { Callout, Intent, Link, Button } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/network-switcher';
import { useEnvironment } from '@vegaprotocol/environment';
import { Trans, useTranslation } from 'react-i18next';
import { Link as RouteLink } from 'react-router-dom';

View File

@ -2,13 +2,14 @@ import { t } from '@vegaprotocol/react-helpers';
import { Link, Splash } from '@vegaprotocol/ui-toolkit';
import type { EthereumConfig } from '@vegaprotocol/web3';
import { useEthereumConfig } from '@vegaprotocol/web3';
import { useEnvironment } from '@vegaprotocol/network-switcher';
import { useEnvironment } from '@vegaprotocol/environment';
import { Heading } from '../../components/heading';
import { SplashLoader } from '../../components/splash-loader';
import { ENV } from '../../config/env';
const Contracts = () => {
const { config } = useEthereumConfig();
const { ADDRESSES, ETHERSCAN_URL } = useEnvironment();
const { ETHERSCAN_URL } = useEnvironment();
if (!config) {
return (
@ -47,7 +48,7 @@ const Contracts = () => {
</div>
);
})}
{Object.entries(ADDRESSES).map(([key, value]) => (
{Object.entries(ENV.addresses).map(([key, value]) => (
<div
key={key}
style={{ display: 'flex', justifyContent: 'space-between' }}

View File

@ -1,7 +1,7 @@
import { useTranslation } from 'react-i18next';
import { Callout, Link, Intent, Splash } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/network-switcher';
import { useEnvironment } from '@vegaprotocol/environment';
import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
import { useTranches } from '../../../hooks/use-tranches';
import type { BigNumber } from '../../../lib/bignumber';

View File

@ -1,5 +1,5 @@
import { Button, Callout, Link, Loader } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/network-switcher';
import { useEnvironment } from '@vegaprotocol/environment';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Link as RouteLink } from 'react-router-dom';

View File

@ -5,7 +5,7 @@ import { Link as RouteLink } from 'react-router-dom';
import { BulletHeader } from '../../components/bullet-header';
import { Link } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/network-switcher';
import { useEnvironment } from '@vegaprotocol/environment';
import { Links } from '../../config';
import {
AppStateActionType,

View File

@ -2,7 +2,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/network-switcher';
import { useEnvironment } from '@vegaprotocol/environment';
import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
import { BigNumber } from '../../lib/bignumber';
import { formatNumber } from '../../lib/format-number';

View File

@ -7,7 +7,7 @@ import { useParams } from 'react-router';
import { Navigate } from 'react-router-dom';
import { useOutletContext } from 'react-router-dom';
import { useEnvironment } from '@vegaprotocol/network-switcher';
import { useEnvironment } from '@vegaprotocol/environment';
import { BigNumber } from '../../lib/bignumber';
import { formatNumber } from '../../lib/format-number';
import { TrancheItem } from '../redemption/tranche-item';

View File

@ -5,7 +5,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/network-switcher';
import { useEnvironment } from '@vegaprotocol/environment';
import { Heading } from '../../components/heading';
import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
import { SplashLoader } from '../../components/splash-loader';

View File

@ -17,7 +17,6 @@
"env": {
"TRADING_TEST_VEGA_WALLET_NAME": "UI_Trading_Test",
"ETHEREUM_PROVIDER_URL": "https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8",
"ETHEREUM_CHAIN_ID": 3,
"VEGA_PUBLIC_KEY": "47836c253520d2661bf5bed6339c0de08fd02cf5d4db0efee3b4373f20c7d278",
"VEGA_PUBLIC_KEY2": "1a18cdcaaa4f44a57b35a4e9b77e0701c17a476f2b407620f8c17371740cf2e4",
"TRUNCATED_VEGA_PUBLIC_KEY": "47836c…c7d278",

View File

@ -1,7 +1,7 @@
# App configuration variables
NX_VEGA_ENV=TESTNET
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json
NX_VEGA_URL=https://lb.testnet.vega.xyz/query
NX_ETHEREUM_CHAIN_ID=3
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://ropsten.etherscan.io
NX_VEGA_NETWORKS={\"MAINNET\":\"https://alpha.console.vega.xyz\"}

View File

@ -1,7 +1,7 @@
# App configuration variables
NX_VEGA_ENV=DEVNET
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/devnet-network.json
NX_VEGA_URL=https://n04.d.vega.xyz/query
NX_VEGA_NETWORKS={\"MAINNET\":\"https://alpha.console.vega.xyz\"}
NX_ETHEREUM_CHAIN_ID=3
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://ropsten.etherscan.io

View File

@ -1,7 +1,7 @@
# App configuration variables
NX_VEGA_ENV=MAINNET
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mainnet-network.json
NX_VEGA_URL=https://api.token.vega.xyz/query
NX_VEGA_NETWORKS='{\"MAINNET\":\"https://alpha.console.vega.xyz\"}'
NX_ETHEREUM_CHAIN_ID=1
NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://etherscan.io

View File

@ -1,7 +1,7 @@
# App configuration variables
NX_VEGA_ENV=STAGNET
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet1-network.json
NX_VEGA_URL=https://n03.s.vega.xyz/query
NX_VEGA_NETWORKS='{\"MAINNET\":\"https://alpha.console.vega.xyz\"}'
NX_ETHEREUM_CHAIN_ID=3
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://ropsten.etherscan.io

View File

@ -1,7 +1,7 @@
# App configuration variables
NX_VEGA_ENV=STAGNET2
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet2-network.json
NX_VEGA_URL=https://n03.stagnet2.vega.xyz/query
NX_VEGA_NETWORKS='{\"MAINNET\":\"https://alpha.console.vega.xyz\"}'
NX_ETHEREUM_CHAIN_ID=3
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://ropsten.etherscan.io

View File

@ -1,7 +1,7 @@
# App configuration variables
NX_VEGA_ENV=TESTNET
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json
NX_VEGA_URL=https://lb.testnet.vega.xyz/query
NX_VEGA_NETWORKS='{\"MAINNET\":\"https://alpha.console.vega.xyz\"}'
NX_ETHEREUM_CHAIN_ID=3
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://ropsten.etherscan.io

View File

@ -26,11 +26,13 @@ Example configurations are provided here:
There are a few different configuration options offered for this app:
The network configuration for the app
| **Flag** | **Purpose** |
| -------------------------- | -------------------------------------------------------------------------------------------------------- |
| `NX_VEGA_ENV` | The name of the currently connected vega environment |
| `NX_VEGA_CONFIG_URL` | The network configuration for the app |
| `NX_VEGA_URL` | The GraphQL query endpoint of a [Vega data node](https://github.com/vegaprotocol/networks#data-node) |
| `NX_ETHEREUM_CHAIN_ID` | The ID of the Ethereum chain the currently connected Vega Network uses. E.g. Ropsten (3) for testnet |
| `NX_ETHEREUM_PROVIDER_URL` | The Ethereum Provider URL for getting data from the Ethereum network, for example Infura or a local node |
| `NX_ETHERSCAN_URL` | The Etherscan URL to link Ethereum transactions to |

View File

@ -1,6 +1,8 @@
import { useEagerConnect } from '@vegaprotocol/wallet';
import { Connectors } from '../../lib/vega-connectors';
import type { ReactNode } from 'react';
import { useEagerConnect } from '@vegaprotocol/wallet';
import { NetworkLoader } from '@vegaprotocol/environment';
import { Connectors } from '../../lib/vega-connectors';
import { createClient } from '../../lib/apollo-client';
interface AppLoaderProps {
children: ReactNode;
@ -14,5 +16,5 @@ export function AppLoader({ children }: AppLoaderProps) {
// Get keys from vega wallet immediately
useEagerConnect(Connectors);
return <>{children}</>;
return <NetworkLoader createClient={createClient}>{children}</NetworkLoader>;
}

View File

@ -5,7 +5,7 @@ import { Web3Container } from './web3-container';
import type { useWeb3React } from '@web3-react/core';
import type { NetworkParamsQuery } from '@vegaprotocol/web3';
import { NETWORK_PARAMS_QUERY } from '@vegaprotocol/web3';
import { EnvironmentProvider } from '@vegaprotocol/network-switcher';
import { EnvironmentProvider } from '@vegaprotocol/environment';
const defaultHookValue = {
isActive: false,

View File

@ -8,7 +8,7 @@ import { useWeb3React } from '@web3-react/core';
import type { ReactNode } from 'react';
import { useEffect, useState, useMemo } from 'react';
import { t } from '@vegaprotocol/react-helpers';
import { useEnvironment } from '@vegaprotocol/network-switcher';
import { useEnvironment } from '@vegaprotocol/environment';
import { createConnectors } from '../../lib/web3-connectors';
interface Web3ContainerProps {
@ -18,14 +18,15 @@ interface Web3ContainerProps {
export const Web3Container = ({ children }: Web3ContainerProps) => {
const [dialogOpen, setDialogOpen] = useState(false);
const { config, loading, error } = useEthereumConfig();
const { ETHEREUM_PROVIDER_URL, ETHEREUM_CHAIN_ID } = useEnvironment();
const Connectors = useMemo(
() => createConnectors(ETHEREUM_PROVIDER_URL, ETHEREUM_CHAIN_ID),
[ETHEREUM_CHAIN_ID, ETHEREUM_PROVIDER_URL]
);
const { ETHEREUM_PROVIDER_URL } = useEnvironment();
const Connectors = useMemo(() => {
if (config?.chain_id) {
return createConnectors(ETHEREUM_PROVIDER_URL, Number(config?.chain_id));
}
}, [config?.chain_id, ETHEREUM_PROVIDER_URL]);
return (
<AsyncRenderer data={config} loading={loading} error={error}>
{config ? (
{Connectors && config && (
<Web3Provider connectors={Connectors}>
<Web3Content
appChainId={Number(config.chain_id)}
@ -40,7 +41,7 @@ export const Web3Container = ({ children }: Web3ContainerProps) => {
desiredChainId={Number(config.chain_id)}
/>
</Web3Provider>
) : null}
)}
</AsyncRenderer>
);
};

View File

@ -1,5 +1,6 @@
import type { AppProps } from 'next/app';
import Head from 'next/head';
import { useRouter } from 'next/router';
import { Navbar } from '../components/navbar';
import { t, ThemeContext, useThemeSwitcher } from '@vegaprotocol/react-helpers';
import {
@ -7,29 +8,27 @@ import {
VegaManageDialog,
VegaWalletProvider,
} from '@vegaprotocol/wallet';
import { NetworkSwitcherDialog } from '@vegaprotocol/network-switcher';
import {
useEnvironment,
EnvironmentProvider,
NetworkSwitcherDialog,
} from '@vegaprotocol/environment';
import { Connectors } from '../lib/vega-connectors';
import { useMemo } from 'react';
import { createClient } from '../lib/apollo-client';
import { ThemeSwitcher } from '@vegaprotocol/ui-toolkit';
import { ApolloProvider } from '@apollo/client';
import { AppLoader } from '../components/app-loader';
import { VegaWalletConnectButton } from '../components/vega-wallet-connect-button';
import './styles.css';
import { useGlobalStore } from '../stores';
import { ENV } from '../lib/config/env';
import { EnvironmentProvider } from '@vegaprotocol/network-switcher';
import { useEnvironment } from '@vegaprotocol/network-switcher';
function AppBody({ Component, pageProps }: AppProps) {
const { push } = useRouter();
const store = useGlobalStore();
const { VEGA_NETWORKS } = useEnvironment();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [theme, toggleTheme] = useThemeSwitcher();
const [, toggleTheme] = useThemeSwitcher();
return (
<ThemeContext.Provider value={theme}>
<div className="h-full dark:bg-black dark:text-white-60 bg-white relative z-0 text-black-60 grid grid-rows-[min-content,1fr]">
<AppLoader>
<div className="flex items-stretch border-b-[7px] border-vega-yellow">
<Navbar />
<div className="flex items-center gap-4 ml-auto mr-8">
@ -62,25 +61,22 @@ function AppBody({ Component, pageProps }: AppProps) {
setDialogOpen={(open) => store.setVegaNetworkSwitcherDialog(open)}
onConnect={({ network }) => {
if (VEGA_NETWORKS[network]) {
window.location.href = VEGA_NETWORKS[network];
push(VEGA_NETWORKS[network] ?? '');
}
}}
/>
</AppLoader>
</div>
</ThemeContext.Provider>
);
}
function VegaTradingApp(props: AppProps) {
const [theme] = useThemeSwitcher();
const client = useMemo(() => createClient(ENV.vegaUrl), []);
return (
<EnvironmentProvider>
<ThemeContext.Provider value={theme}>
<ApolloProvider client={client}>
<VegaWalletProvider>
<AppLoader>
<Head>
<link
rel="preload"
@ -95,10 +91,7 @@ function VegaTradingApp(props: AppProps) {
type="image/x-icon"
href="https://static.vega.xyz/favicon.ico"
/>
<link
rel="stylesheet"
href="https://static.vega.xyz/fonts.css"
/>
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
{['1', 'true'].includes(
process.env['NX_USE_ENV_OVERRIDES'] || ''
) ? (
@ -107,9 +100,7 @@ function VegaTradingApp(props: AppProps) {
) : null}
</Head>
<AppBody {...props} />
</AppLoader>
</VegaWalletProvider>
</ApolloProvider>
</ThemeContext.Provider>
</EnvironmentProvider>
);

View File

@ -3,7 +3,7 @@ import { PageQueryContainer } from '../../../components/page-query-container';
import type { DepositPage } from './__generated__/DepositPage';
import { DepositManager } from '@vegaprotocol/deposits';
import { t } from '@vegaprotocol/react-helpers';
import { useEnvironment } from '@vegaprotocol/network-switcher';
import { useEnvironment } from '@vegaprotocol/environment';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { ASSET_FRAGMENT } from '../../../lib/query-fragments';

View File

@ -0,0 +1,19 @@
# environment
This library was generated with [Nx](https://nx.dev).
## Prerequisites
The environment variables needed to be present for any app consuming this library.
`NX_VEGA_ENV` is the name of the environment.
`NX_VEGA_REST` is the REST endpoint for the environment.
`NX_VEGA_URL` OR `NX_VEGA_CONFIG_URL` - either the network configuration url or a url to a node to directly connect to
For examples, see Block Explorer's .env files [here](../../apps/explorer)
## Running unit tests
Run `nx test environment` to execute the unit tests via [Jest](https://jestjs.io).

View File

@ -1,9 +1,9 @@
module.exports = {
displayName: 'network-switcher',
displayName: 'environment',
preset: '../../jest.preset.js',
transform: {
'^.+\\.[tj]sx?$': 'babel-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/libs/network-switcher',
coverageDirectory: '../../coverage/libs/environment',
};

View File

@ -0,0 +1,4 @@
{
"name": "@vegaprotocol/environment",
"version": "0.0.1"
}

View File

@ -1,6 +1,6 @@
{
"root": "libs/network-switcher",
"sourceRoot": "libs/network-switcher/src",
"root": "libs/environment",
"sourceRoot": "libs/environment/src",
"projectType": "library",
"tags": [],
"targets": {
@ -8,16 +8,16 @@
"executor": "@nrwl/web:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/network-switcher",
"tsConfig": "libs/network-switcher/tsconfig.lib.json",
"project": "libs/network-switcher/package.json",
"entryFile": "libs/network-switcher/src/index.ts",
"outputPath": "dist/libs/environment",
"tsConfig": "libs/environment/tsconfig.lib.json",
"project": "libs/environment/package.json",
"entryFile": "libs/environment/src/index.ts",
"external": ["react/jsx-runtime"],
"rollupConfig": "@nrwl/react/plugins/bundle-rollup",
"compiler": "babel",
"assets": [
{
"glob": "libs/network-switcher/README.md",
"glob": "libs/environment/README.md",
"input": ".",
"output": "."
}
@ -28,14 +28,14 @@
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/network-switcher/**/*.{ts,tsx,js,jsx}"]
"lintFilePatterns": ["libs/environment/**/*.{ts,tsx,js,jsx}"]
}
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["coverage/libs/network-switcher"],
"outputs": ["coverage/libs/environment"],
"options": {
"jestConfig": "libs/network-switcher/jest.config.js",
"jestConfig": "libs/environment/jest.config.js",
"passWithNoTests": true
}
}

View File

@ -1,2 +1,3 @@
export * from './network-loader';
export * from './network-switcher';
export * from './network-switcher-dialog';

View File

@ -0,0 +1 @@
export * from './network-loader';

View File

@ -0,0 +1,162 @@
import { useState, useEffect, useMemo } from 'react';
import type { ReactNode } from 'react';
import type { ApolloClient } from '@apollo/client';
import { ApolloProvider } from '@apollo/client';
import {
Callout,
Intent,
Button,
Icon,
Loader,
} from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/react-helpers';
import { useEnvironment } from '../../hooks';
import type { ConfigStatus } from '../../types';
type MessageComponentProps = {
children: ReactNode;
};
const StatusMessage = ({ children }: MessageComponentProps) => (
<div className="flex items-center fixed bottom-0 right-0 px-16 bg-intent-highlight text-black">
{children}
</div>
);
type ErrorComponentProps = MessageComponentProps & {
children?: ReactNode;
showTryAgain?: boolean;
};
const Error = ({ children, showTryAgain }: ErrorComponentProps) => (
<div>
<div className="mb-16">{children}</div>
{showTryAgain && (
<Button
className="mt-8"
variant="secondary"
onClick={() => window.location.reload()}
>
{t('Try again')}
</Button>
)}
</div>
);
type StatusComponentProps = {
status: ConfigStatus;
children?: ReactNode;
};
const StatusComponent = ({ status, children }: StatusComponentProps) => {
switch (status) {
case 'error-loading-config':
return (
<Callout
title={t('Error')}
intent={Intent.Danger}
iconName="error"
iconDescription={t('Error')}
children={
<Error>
{t('There was an error fetching the network configuration.')}
</Error>
}
/>
);
case 'error-validating-config':
return (
<Callout
title={t('Error')}
intent={Intent.Danger}
iconName="error"
iconDescription={t('Error')}
children={
<Error>
{t('The network configuration for the app is invalid.')}
</Error>
}
/>
);
case 'error-loading-node':
return (
<Callout
title={t('Error')}
intent={Intent.Danger}
iconName="error"
iconDescription={t('Error')}
children={
<Error showTryAgain>{t('Failed to connect to a data node.')}</Error>
}
/>
);
case 'idle':
case 'loading-config':
return (
<>
{children}
<StatusMessage>
<Loader size="small" forceTheme="light" />
<span className="ml-8">{t('Loading configuration...')}</span>
</StatusMessage>
</>
);
case 'loading-node':
return (
<>
{children}
<StatusMessage>
<Loader size="small" forceTheme="light" />
<span className="ml-8">{t('Finding a node...')}</span>
</StatusMessage>
</>
);
case 'success':
return (
<>
{children}
<StatusMessage>
<Icon name="antenna" />
<span className="ml-8">{t("You're connected!")}</span>
</StatusMessage>
</>
);
}
};
type NetworkLoaderProps<T> = {
children?: ReactNode;
skeleton?: ReactNode;
createClient: (url: string) => ApolloClient<T>;
};
export function NetworkLoader<T>({
skeleton,
children,
createClient,
}: NetworkLoaderProps<T>) {
// this is to prevent an error rendering callouts on the server side
const [canShowCallout, setShowCallout] = useState(false);
const { configStatus, VEGA_URL } = useEnvironment();
const client = useMemo(() => {
if (VEGA_URL) {
return createClient(VEGA_URL);
}
return undefined;
}, [VEGA_URL, createClient]);
useEffect(() => {
setShowCallout(true);
}, []);
if (!client) {
return canShowCallout ? (
<div className="h-full min-h-screen flex items-center justify-center">
<StatusComponent status={configStatus}>{skeleton}</StatusComponent>
</div>
) : null;
}
return <ApolloProvider client={client}>{children}</ApolloProvider>;
}

View File

@ -1,8 +1,8 @@
import { useForm, Controller } from 'react-hook-form';
import { Button, Select } from '@vegaprotocol/ui-toolkit';
import type { Networks } from '@vegaprotocol/react-helpers';
import { t } from '@vegaprotocol/react-helpers';
import { useEnvironment } from '../../hooks';
import type { Networks } from '../../types';
type NetworkState = {
network: Networks;

View File

@ -0,0 +1,311 @@
import { renderHook } from '@testing-library/react-hooks';
import type { EnvironmentWithOptionalUrl } from './use-config';
import { useConfig, LOCAL_STORAGE_NETWORK_KEY } from './use-config';
import { Networks } from '../types';
type HostMapping = Record<string, number | Error>;
const mockHostsMap: HostMapping = {
'https://host1.com': 300,
'https://host2.com': 500,
'https://host3.com': 100,
'https://host4.com': 650,
};
const hostList = Object.keys(mockHostsMap);
const mockEnvironment: EnvironmentWithOptionalUrl = {
VEGA_ENV: Networks.TESTNET,
VEGA_CONFIG_URL: 'https://vega.url/config.json',
VEGA_NETWORKS: {},
ETHEREUM_PROVIDER_URL: 'https://ethereum.provider',
ETHERSCAN_URL: 'https://etherscan.url',
};
function setupFetch(configUrl: string, hostMap: HostMapping) {
const hostUrls = Object.keys(hostMap);
return (url: RequestInfo) => {
if (url === configUrl) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ hosts: hostUrls }),
} as Response);
}
if (hostUrls.includes(url as string)) {
const value = hostMap[url as string];
return new Promise<Response>((resolve, reject) => {
if (typeof value === 'number') {
setTimeout(() => {
resolve({ ok: true } as Response);
}, value);
} else {
reject(value);
}
});
}
return Promise.resolve({
ok: true,
} as Response);
};
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
const noop = () => {};
global.fetch = jest.fn();
const mockUpdate = jest.fn();
beforeEach(() => {
jest.useFakeTimers();
mockUpdate.mockClear();
window.localStorage.clear();
// @ts-ignore typescript doesn't recognise the mocked instance
global.fetch.mockReset();
// @ts-ignore typescript doesn't recognise the mocked instance
global.fetch.mockImplementation(
setupFetch(mockEnvironment.VEGA_CONFIG_URL ?? '', mockHostsMap)
);
});
afterAll(() => {
// @ts-ignore: typescript doesn't recognise the mocked fetch instance
fetch.mockRestore();
});
describe('useConfig hook', () => {
it('has an initial success state when the environment already has a URL', async () => {
const mockEnvWithUrl = {
...mockEnvironment,
VEGA_URL: 'https://some.url/query',
};
const { result } = renderHook(() => useConfig(mockEnvWithUrl, mockUpdate));
expect(fetch).not.toHaveBeenCalled();
expect(mockUpdate).not.toHaveBeenCalled();
expect(result.current.status).toBe('success');
});
it('updates the environment with a host url from the network configuration', async () => {
const allowedStatuses = [
'idle',
'loading-config',
'loading-node',
'success',
];
const { result, waitForNextUpdate } = renderHook(() =>
useConfig(mockEnvironment, mockUpdate)
);
await waitForNextUpdate();
jest.runAllTimers();
await waitForNextUpdate();
expect(result.current.status).toBe('success');
result.all.forEach((state) => {
expect(allowedStatuses).toContain('status' in state && state.status);
});
// fetches config
expect(fetch).toHaveBeenCalledWith(mockEnvironment.VEGA_CONFIG_URL);
// calls each node
hostList.forEach((url) => {
expect(fetch).toHaveBeenCalledWith(url);
});
// updates the environment
expect(hostList).toContain(mockUpdate.mock.calls[0][0]({}).VEGA_URL);
});
it('uses the host from the configuration which responds first', async () => {
const shortestResponseTime = Object.values(mockHostsMap).sort()[0];
const expectedHost = hostList.find((url: keyof typeof mockHostsMap) => {
return mockHostsMap[url] === shortestResponseTime;
});
const { result, waitForNextUpdate } = renderHook(() =>
useConfig(mockEnvironment, mockUpdate)
);
await waitForNextUpdate();
jest.runAllTimers();
await waitForNextUpdate();
expect(result.current.status).toBe('success');
expect(mockUpdate.mock.calls[0][0]({}).VEGA_URL).toBe(expectedHost);
});
it('ignores failing hosts and uses one which returns a success response', async () => {
const mockHostsMapScoped = {
'https://host1.com': 350,
'https://host2.com': new Error('Server error'),
'https://host3.com': 230,
'https://host4.com': new Error('Server error'),
};
// @ts-ignore typescript doesn't recognise the mocked instance
global.fetch.mockImplementation(
setupFetch(mockEnvironment.VEGA_CONFIG_URL ?? '', mockHostsMapScoped)
);
const { result, waitForNextUpdate } = renderHook(() =>
useConfig(mockEnvironment, mockUpdate)
);
await waitForNextUpdate();
jest.runAllTimers();
await waitForNextUpdate();
expect(result.current.status).toBe('success');
expect(mockUpdate.mock.calls[0][0]({}).VEGA_URL).toBe('https://host3.com');
});
it('returns the correct error status for when the config cannot be accessed', async () => {
// @ts-ignore typescript doesn't recognise the mocked instance
global.fetch.mockImplementation((url: RequestInfo) => {
if (url === mockEnvironment.VEGA_CONFIG_URL) {
return Promise.reject(new Error('Server error'));
}
return Promise.resolve({ ok: true } as Response);
});
const { result, waitForNextUpdate } = renderHook(() =>
useConfig(mockEnvironment, mockUpdate)
);
await waitForNextUpdate();
expect(result.current.status).toBe('error-loading-config');
expect(mockUpdate).not.toHaveBeenCalled();
});
it('returns the correct error status for when the config is not valid', async () => {
// @ts-ignore typescript doesn't recognise the mocked instance
global.fetch.mockImplementation((url: RequestInfo) => {
if (url === mockEnvironment.VEGA_CONFIG_URL) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ some: 'data' }),
});
}
return Promise.resolve({ ok: true } as Response);
});
const { result, waitForNextUpdate } = renderHook(() =>
useConfig(mockEnvironment, mockUpdate)
);
await waitForNextUpdate();
expect(result.current.status).toBe('error-validating-config');
expect(mockUpdate).not.toHaveBeenCalled();
});
it('returns the correct error status for when no hosts can be accessed', async () => {
const mockHostsMapScoped = {
'https://host1.com': new Error('Server error'),
'https://host2.com': new Error('Server error'),
'https://host3.com': new Error('Server error'),
'https://host4.com': new Error('Server error'),
};
// @ts-ignore typescript doesn't recognise the mocked instance
global.fetch.mockImplementation(
setupFetch(mockEnvironment.VEGA_CONFIG_URL ?? '', mockHostsMapScoped)
);
const { result, waitForNextUpdate } = renderHook(() =>
useConfig(mockEnvironment, mockUpdate)
);
await waitForNextUpdate();
expect(result.current.status).toBe('error-loading-node');
expect(mockUpdate).not.toHaveBeenCalled();
});
it('caches the list of networks', async () => {
const run1 = renderHook(() => useConfig(mockEnvironment, mockUpdate));
await run1.waitForNextUpdate();
jest.runAllTimers();
await run1.waitForNextUpdate();
expect(run1.result.current.status).toBe('success');
expect(fetch).toHaveBeenCalledWith(mockEnvironment.VEGA_CONFIG_URL);
// @ts-ignore typescript doesn't recognise the mocked instance
fetch.mockClear();
const run2 = renderHook(() => useConfig(mockEnvironment, mockUpdate));
jest.runAllTimers();
await run2.waitForNextUpdate();
expect(run2.result.current.status).toBe('success');
expect(fetch).not.toHaveBeenCalledWith(mockEnvironment.VEGA_CONFIG_URL);
});
it('caches the list of networks between runs', async () => {
const run1 = renderHook(() => useConfig(mockEnvironment, mockUpdate));
await run1.waitForNextUpdate();
jest.runAllTimers();
await run1.waitForNextUpdate();
expect(run1.result.current.status).toBe('success');
expect(fetch).toHaveBeenCalledWith(mockEnvironment.VEGA_CONFIG_URL);
// @ts-ignore typescript doesn't recognise the mocked instance
fetch.mockClear();
const run2 = renderHook(() => useConfig(mockEnvironment, mockUpdate));
jest.runAllTimers();
await run2.waitForNextUpdate();
expect(run2.result.current.status).toBe('success');
expect(fetch).not.toHaveBeenCalledWith(mockEnvironment.VEGA_CONFIG_URL);
});
it('refetches the network configuration and resets the cache when malformed data found in the storage', async () => {
window.localStorage.setItem(LOCAL_STORAGE_NETWORK_KEY, '{not:{valid:{json');
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(noop);
const run1 = renderHook(() => useConfig(mockEnvironment, mockUpdate));
await run1.waitForNextUpdate();
jest.runAllTimers();
await run1.waitForNextUpdate();
expect(run1.result.current.status).toBe('success');
expect(fetch).toHaveBeenCalledWith(mockEnvironment.VEGA_CONFIG_URL);
expect(consoleWarnSpy).toHaveBeenCalled();
consoleWarnSpy.mockRestore();
});
it('refetches the network configuration and resets the cache when invalid data found in the storage', async () => {
window.localStorage.setItem(
LOCAL_STORAGE_NETWORK_KEY,
JSON.stringify({ invalid: 'data' })
);
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(noop);
const run1 = renderHook(() => useConfig(mockEnvironment, mockUpdate));
await run1.waitForNextUpdate();
jest.runAllTimers();
await run1.waitForNextUpdate();
expect(run1.result.current.status).toBe('success');
expect(fetch).toHaveBeenCalledWith(mockEnvironment.VEGA_CONFIG_URL);
expect(consoleSpy).toHaveBeenCalled();
consoleSpy.mockRestore();
});
});

View File

@ -0,0 +1,122 @@
import type { Dispatch, SetStateAction } from 'react';
import { useState, useEffect } from 'react';
import { LocalStorage } from '@vegaprotocol/react-helpers';
import type { Environment, Configuration, ConfigStatus } from '../types';
import { validateConfiguration } from '../utils/validate-configuration';
import { promiseRaceToSuccess } from '../utils/promise-race-success';
export const LOCAL_STORAGE_NETWORK_KEY = 'vegaNetworkConfig';
export type EnvironmentWithOptionalUrl = Partial<Environment> &
Omit<Environment, 'VEGA_URL'>;
const requestToNode = async (url: string, index: number): Promise<number> => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed connecting to node: ${url}.`);
}
return index;
};
const getCachedConfig = () => {
const value = LocalStorage.getItem(LOCAL_STORAGE_NETWORK_KEY);
if (value) {
try {
const config = JSON.parse(value) as Configuration;
const hasError = validateConfiguration(config);
if (hasError) {
throw new Error('Invalid configuration found in the storage.');
}
return config;
} catch (err) {
LocalStorage.removeItem(LOCAL_STORAGE_NETWORK_KEY);
console.warn(
'Malformed data found for network configuration. Removed and continuing...'
);
}
}
return undefined;
};
export const useConfig = (
environment: EnvironmentWithOptionalUrl,
updateEnvironment: Dispatch<SetStateAction<Environment>>
) => {
const [config, setConfig] = useState<Configuration | undefined>(
getCachedConfig()
);
const [status, setStatus] = useState<ConfigStatus>(
!environment.VEGA_URL ? 'idle' : 'success'
);
useEffect(() => {
if (!config && status === 'idle') {
(async () => {
setStatus('loading-config');
try {
const response = await fetch(environment.VEGA_CONFIG_URL ?? '');
const configData: Configuration = await response.json();
if (validateConfiguration(configData)) {
setStatus('error-validating-config');
return;
}
setConfig({ hosts: configData.hosts });
LocalStorage.setItem(
LOCAL_STORAGE_NETWORK_KEY,
JSON.stringify({ hosts: configData.hosts })
);
} catch (err) {
setStatus('error-loading-config');
}
})();
}
// load config only once per runtime
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [environment.VEGA_CONFIG_URL, !!config, status, setStatus, setConfig]);
useEffect(() => {
if (
config &&
!['loading-node', 'success', 'error-loading-node'].includes(status)
) {
(async () => {
setStatus('loading-node');
// if there's only one configured node to choose from, set is as the env url
if (config.hosts.length === 1) {
setStatus('success');
updateEnvironment((prevEnvironment) => ({
...prevEnvironment,
VEGA_URL: config.hosts[0],
}));
return;
}
// when there are multiple possible hosts, set the env url to the node which responds first
try {
const requests = config.hosts.map(requestToNode);
const index = await promiseRaceToSuccess(requests);
setStatus('success');
updateEnvironment((prevEnvironment) => ({
...prevEnvironment,
VEGA_URL: config.hosts[index],
}));
} catch (err) {
setStatus('error-loading-node');
}
})();
}
// load config only once per runtime
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [status, !!config, setStatus, updateEnvironment]);
return {
status,
};
};

View File

@ -0,0 +1,228 @@
import type { ComponentProps } from 'react';
import { renderHook } from '@testing-library/react-hooks';
import type { EnvironmentState } from './use-environment';
import { useEnvironment, EnvironmentProvider } from './use-environment';
import { Networks } from '../types';
const MockWrapper = (props: ComponentProps<typeof EnvironmentProvider>) => {
return <EnvironmentProvider {...props} />;
};
const MOCK_HOST = 'https://vega.host/query';
global.fetch = jest.fn();
// eslint-disable-next-line @typescript-eslint/no-empty-function
const noop = () => {};
const mockFetch = (url: RequestInfo) => {
if (url === mockEnvironmentState.VEGA_CONFIG_URL) {
return Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
hosts: [MOCK_HOST],
}),
} as Response);
}
return Promise.resolve({ ok: true } as Response);
};
const mockEnvironmentState: EnvironmentState = {
configStatus: 'success',
VEGA_URL: 'https://vega.xyz',
VEGA_ENV: Networks.TESTNET,
VEGA_CONFIG_URL: 'https://vega.xyz/testnet-config.json',
VEGA_NETWORKS: {
TESTNET: 'https://testnet.url',
STAGNET: 'https://stagnet.url',
MAINNET: 'https://mainnet.url',
},
ETHEREUM_PROVIDER_URL: 'https://ether.provider',
ETHERSCAN_URL: 'https://etherscan.url',
};
beforeEach(() => {
// @ts-ignore typscript doesn't recognise the mock implementation
global.fetch.mockReset();
// @ts-ignore typscript doesn't recognise the mock implementation
global.fetch.mockImplementation(mockFetch);
window.localStorage.clear();
process.env['NX_VEGA_URL'] = mockEnvironmentState.VEGA_URL;
process.env['NX_VEGA_ENV'] = mockEnvironmentState.VEGA_ENV;
process.env['NX_VEGA_CONFIG_URL'] = mockEnvironmentState.VEGA_CONFIG_URL;
process.env['NX_ETHEREUM_PROVIDER_URL'] =
mockEnvironmentState.ETHEREUM_PROVIDER_URL;
process.env['NX_ETHERSCAN_URL'] = mockEnvironmentState.ETHERSCAN_URL;
process.env['NX_VEGA_NETWORKS'] = JSON.stringify(
mockEnvironmentState.VEGA_NETWORKS
);
});
afterAll(() => {
// @ts-ignore: typescript doesn't recognise the mocked fetch instance
fetch.mockRestore();
window.localStorage.clear();
delete process.env['NX_VEGA_URL'];
delete process.env['NX_VEGA_ENV'];
delete process.env['NX_VEGA_CONFIG_URL'];
delete process.env['NX_ETHEREUM_PROVIDER_URL'];
delete process.env['NX_ETHERSCAN_URL'];
delete process.env['NX_VEGA_NETWORKS'];
});
describe('useEnvironment hook', () => {
it('transforms and exposes values from the environment', () => {
const { result } = renderHook(() => useEnvironment(), {
wrapper: MockWrapper,
});
expect(result.error).toBe(undefined);
expect(result.current).toEqual(mockEnvironmentState);
});
it('allows for the VEGA_CONFIG_URL to be missing when there is a VEGA_URL present', () => {
delete process.env['NX_VEGA_CONFIG_URL'];
const { result } = renderHook(() => useEnvironment(), {
wrapper: MockWrapper,
});
expect(result.error).toBe(undefined);
expect(result.current).toEqual({
...mockEnvironmentState,
VEGA_CONFIG_URL: undefined,
});
});
it('allows for the VEGA_URL to be missing when there is a VEGA_CONFIG_URL present', async () => {
delete process.env['NX_VEGA_URL'];
const { result, waitForNextUpdate } = renderHook(() => useEnvironment(), {
wrapper: MockWrapper,
});
await waitForNextUpdate();
expect(result.error).toBe(undefined);
expect(result.current).toEqual({
...mockEnvironmentState,
VEGA_URL: MOCK_HOST,
});
});
it('allows for the VEGA_NETWORKS to be missing from the environment', () => {
delete process.env['NX_VEGA_NETWORKS'];
const { result } = renderHook(() => useEnvironment(), {
wrapper: MockWrapper,
});
expect(result.error).toBe(undefined);
expect(result.current).toEqual({
...mockEnvironmentState,
VEGA_NETWORKS: {},
});
});
it('throws a validation error when NX_VEGA_ENV is not found in the environment', async () => {
delete process.env['NX_VEGA_ENV'];
const { result } = renderHook(() => useEnvironment(), {
wrapper: MockWrapper,
});
expect(result.error?.message).toContain(
`NX_VEGA_ENV is invalid, received "undefined" instead of: 'CUSTOM' | 'TESTNET' | 'STAGNET' | 'STAGNET2' | 'DEVNET' | 'MAINNET'`
);
});
it('throws a validation error when VEGA_ENV is not a valid network', () => {
process.env['NX_VEGA_ENV'] = 'SOMETHING';
const { result } = renderHook(() => useEnvironment(), {
wrapper: MockWrapper,
});
expect(result.error).not.toContain(
`NX_VEGA_ENV is invalid, received "SOMETHING" instead of: CUSTOM | TESTNET | STAGNET | STAGNET2 | DEVNET | MAINNET`
);
});
it('when VEGA_NETWORKS is not a valid json, prints a warning and continues without using the value from it', () => {
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(noop);
process.env['NX_VEGA_NETWORKS'] = '{not:{valid:json';
const { result } = renderHook(() => useEnvironment(), {
wrapper: MockWrapper,
});
expect(result.error).toBe(undefined);
expect(result.current).toEqual({
...mockEnvironmentState,
VEGA_NETWORKS: {},
});
expect(consoleWarnSpy).toHaveBeenCalled();
consoleWarnSpy.mockRestore();
});
it('throws a validation error when VEGA_NETWORKS is has an invalid network as a key', () => {
process.env['NX_VEGA_NETWORKS'] = JSON.stringify({
NOT_A_NETWORK: 'https://somewhere.url',
});
const { result } = renderHook(() => useEnvironment(), {
wrapper: MockWrapper,
});
expect(result.error?.message).toContain(
`All keys in NX_VEGA_NETWORKS must represent a valid environment: CUSTOM | TESTNET | STAGNET | STAGNET2 | DEVNET | MAINNET`
);
});
it('throws a validation error when both VEGA_URL and VEGA_CONFIG_URL are missing in the environment', () => {
delete process.env['NX_VEGA_URL'];
delete process.env['NX_VEGA_CONFIG_URL'];
const { result } = renderHook(() => useEnvironment(), {
wrapper: MockWrapper,
});
expect(result.error?.message).toContain(
`Must provide either NX_VEGA_CONFIG_URL or NX_VEGA_URL in the environment.`
);
});
it.each`
env | etherscanUrl | providerUrl
${Networks.DEVNET} | ${'https://ropsten.etherscan.io'} | ${'https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8'}
${Networks.TESTNET} | ${'https://ropsten.etherscan.io'} | ${'https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8'}
${Networks.STAGNET} | ${'https://ropsten.etherscan.io'} | ${'https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8'}
${Networks.STAGNET2} | ${'https://ropsten.etherscan.io'} | ${'https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8'}
${Networks.MAINNET} | ${'https://etherscan.io'} | ${'https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8'}
`(
'uses correct default ethereum connection variables in $env',
async ({ env, etherscanUrl, providerUrl }) => {
process.env['NX_VEGA_ENV'] = env;
delete process.env['NX_ETHEREUM_PROVIDER_URL'];
delete process.env['NX_ETHERSCAN_URL'];
const { result } = renderHook(() => useEnvironment(), {
wrapper: MockWrapper,
});
expect(result.error).toBe(undefined);
expect(result.current).toEqual({
...mockEnvironmentState,
VEGA_ENV: env,
ETHEREUM_PROVIDER_URL: providerUrl,
ETHERSCAN_URL: etherscanUrl,
});
}
);
it('throws a validation error when NX_ETHERSCAN_URL is not a valid url', async () => {
process.env['NX_ETHERSCAN_URL'] = 'invalid-url';
const { result } = renderHook(() => useEnvironment(), {
wrapper: MockWrapper,
});
expect(result.error?.message).toContain(
`The NX_ETHERSCAN_URL environment variable must be a valid url`
);
});
it('throws a validation error when NX_ETHEREUM_PROVIDER_URL is not a valid url', async () => {
process.env['NX_ETHEREUM_PROVIDER_URL'] = 'invalid-url';
const { result } = renderHook(() => useEnvironment(), {
wrapper: MockWrapper,
});
expect(result.error?.message).toContain(
`The NX_ETHEREUM_PROVIDER_URL environment variable must be a valid url`
);
});
});

View File

@ -0,0 +1,50 @@
import type { ReactNode } from 'react';
import { useState, createContext, useContext } from 'react';
import { useConfig } from './use-config';
import { compileEnvironment } from '../utils/compile-environment';
import { validateEnvironment } from '../utils/validate-environment';
import type { Environment, RawEnvironment, ConfigStatus } from '../types';
type EnvironmentProviderProps = {
definitions?: Partial<RawEnvironment>;
children?: ReactNode;
};
export type EnvironmentState = Environment & {
configStatus: ConfigStatus;
};
const EnvironmentContext = createContext({} as EnvironmentState);
export const EnvironmentProvider = ({
definitions,
children,
}: EnvironmentProviderProps) => {
const [environment, updateEnvironment] = useState<Environment>(
compileEnvironment(definitions)
);
const { status: configStatus } = useConfig(environment, updateEnvironment);
const errorMessage = validateEnvironment(environment);
if (errorMessage) {
throw new Error(errorMessage);
}
return (
<EnvironmentContext.Provider value={{ ...environment, configStatus }}>
{children}
</EnvironmentContext.Provider>
);
};
export const useEnvironment = () => {
const context = useContext(EnvironmentContext);
if (context === undefined) {
throw new Error(
'Error running "useEnvironment". No context found, make sure your component is wrapped in an <EnvironmentProvider />.'
);
}
return context;
};

View File

@ -3,3 +3,6 @@ export * from './components';
// Hooks
export * from './hooks';
// Types
export * from './types';

View File

@ -0,0 +1,27 @@
import type z from 'zod';
import type { configSchema } from './utils/validate-configuration';
import type { envSchema } from './utils/validate-environment';
import { Networks, ENV_KEYS } from './utils/validate-environment';
export { ENV_KEYS, Networks };
export type Environment = z.infer<typeof envSchema> & {
// provide this manually, zod fails to compile the correct type fot VEGA_NETWORKS
VEGA_NETWORKS: Partial<Record<Networks, string>>;
};
export type EnvKey = keyof Environment;
export type RawEnvironment = Record<EnvKey, string>;
export type Configuration = z.infer<typeof configSchema>;
export type ConfigStatus =
| 'idle'
| 'success'
| 'loading-config'
| 'loading-node'
| 'error-loading-config'
| 'error-validating-config'
| 'error-loading-node';

View File

@ -0,0 +1,102 @@
import type { RawEnvironment, EnvKey, Environment } from '../types';
import { Networks, ENV_KEYS } from '../types';
declare global {
interface Window {
_env_?: Record<string, string>;
}
}
const isBrowser = typeof window !== 'undefined';
const getDefaultEtherumProviderUrl = (env: Networks) => {
return env === Networks.MAINNET
? 'https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8'
: 'https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8';
};
const getDefaultEtherscanUrl = (env: Networks) => {
return env === Networks.MAINNET
? 'https://etherscan.io'
: 'https://ropsten.etherscan.io';
};
const transformValue = (key: EnvKey, value?: string) => {
switch (key) {
case 'VEGA_ENV':
return value as Networks;
case 'VEGA_NETWORKS': {
if (value) {
try {
return JSON.parse(value);
} catch (e) {
console.warn(
'Error parsing the "NX_VEGA_NETWORKS" environment variable. Make sure it has a valid JSON format.'
);
return {};
}
}
return {};
}
default:
return value;
}
};
const getBundledEnvironmentValue = (key: EnvKey) => {
switch (key) {
// need to have these hardcoded so on build time they can be replaced with the relevant environment variable
case 'VEGA_URL':
return process.env['NX_VEGA_URL'];
case 'VEGA_ENV':
return process.env['NX_VEGA_ENV'];
case 'VEGA_CONFIG_URL':
return process.env['NX_VEGA_CONFIG_URL'];
case 'ETHEREUM_PROVIDER_URL':
return process.env['NX_ETHEREUM_PROVIDER_URL'];
case 'ETHERSCAN_URL':
return process.env['NX_ETHERSCAN_URL'];
case 'VEGA_NETWORKS':
return process.env['NX_VEGA_NETWORKS'];
}
};
const getValue = (key: EnvKey, definitions: Partial<RawEnvironment> = {}) => {
if (!isBrowser) {
return transformValue(
key,
definitions[key] ?? getBundledEnvironmentValue(key)
);
}
return transformValue(
key,
definitions[key] ?? window._env_?.[key] ?? getBundledEnvironmentValue(key)
);
};
export const compileEnvironment = (
definitions?: Partial<RawEnvironment>
): Environment => {
const environment = ENV_KEYS.reduce((acc, key) => {
const value = getValue(key, definitions);
if (value) {
return {
...acc,
[key]: value,
};
}
return acc;
}, {} as Environment);
return {
// @ts-ignore enable using default object props
ETHERSCAN_URL: getDefaultEtherscanUrl(environment['VEGA_ENV']),
// @ts-ignore enable using default object props
ETHEREUM_PROVIDER_URL: getDefaultEtherumProviderUrl(
environment['VEGA_ENV']
),
...environment,
};
};

View File

@ -0,0 +1,19 @@
import type { ZodIssue } from 'zod';
import { ZodError } from 'zod';
export const compileErrors = (
headline: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
error: any,
compileIssue?: (issue: ZodIssue) => string
) => {
if (error instanceof ZodError) {
return error.issues.reduce((acc, issue) => {
return (
acc + `\n - ${compileIssue ? compileIssue(issue) : issue.message}`
);
}, `${headline}:`);
}
return `${headline}${error?.message ? `: ${error.message}` : ''}`;
};

View File

@ -0,0 +1,22 @@
export function promiseRaceToSuccess<T>(requests: Array<Promise<T>>) {
return new Promise<T>((resolve, reject) => {
let hasResolved = false;
const failures = [];
requests.forEach((req) => {
req
.then((res) => {
if (!hasResolved) {
resolve(res);
hasResolved = true;
}
})
.catch((err) => {
failures.push(err);
if (failures.length === requests.length) {
reject(err);
}
});
});
});
}

View File

@ -0,0 +1,19 @@
import z from 'zod';
import type { Configuration } from '../types';
import { compileErrors } from './compile-errors';
export const configSchema = z.object({
hosts: z.array(z.string()),
});
export const validateConfiguration = (
config: Configuration
): string | undefined => {
try {
configSchema.parse(config);
return undefined;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
return compileErrors('Error processing the vega app configuration', err);
}
};

View File

@ -0,0 +1,84 @@
import type { ZodIssue } from 'zod';
import z from 'zod';
import type { Environment } from '../types';
import { compileErrors } from './compile-errors';
export enum Networks {
CUSTOM = 'CUSTOM',
TESTNET = 'TESTNET',
STAGNET = 'STAGNET',
STAGNET2 = 'STAGNET2',
DEVNET = 'DEVNET',
MAINNET = 'MAINNET',
}
const schemaObject = {
VEGA_URL: z.optional(z.string()),
VEGA_CONFIG_URL: z.optional(z.string()),
ETHEREUM_PROVIDER_URL: z.string().url({
message:
'The NX_ETHEREUM_PROVIDER_URL environment variable must be a valid url',
}),
ETHERSCAN_URL: z.string().url({
message: 'The NX_ETHERSCAN_URL environment variable must be a valid url',
}),
VEGA_ENV: z.nativeEnum(Networks),
VEGA_NETWORKS: z
.object(
Object.keys(Networks).reduce(
(acc, env) => ({
...acc,
[env]: z.optional(z.string()),
}),
{}
)
)
.strict({
message: `All keys in NX_VEGA_NETWORKS must represent a valid environment: ${Object.keys(
Networks
).join(' | ')}`,
}),
};
export const ENV_KEYS = Object.keys(schemaObject) as Array<
keyof typeof schemaObject
>;
const compileIssue = (issue: ZodIssue) => {
switch (issue.code) {
case 'invalid_type':
return `NX_${issue.path[0]} is invalid, received "${issue.received}" instead of: ${issue.expected}`;
case 'invalid_enum_value':
return `NX_${issue.path[0]} is invalid, received "${
issue.received
}" instead of: ${issue.options.join(' | ')}`;
default:
return issue.message;
}
};
export const envSchema = z.object(schemaObject).refine(
(data) => {
return !(!data.VEGA_URL && !data.VEGA_CONFIG_URL);
},
{
message:
'Must provide either NX_VEGA_CONFIG_URL or NX_VEGA_URL in the environment.',
}
);
export const validateEnvironment = (
environment: Environment
): string | undefined => {
try {
envSchema.parse(environment);
return undefined;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
return compileErrors(
'Error processing the vega app environment',
err,
compileIssue
);
}
};

View File

@ -1,17 +0,0 @@
# network-switcher
This library was generated with [Nx](https://nx.dev).
## Prerequisites
Two environment variables need to be present for any app consuming this library.
`NX_VEGA_ENV` is the name of the environment.
`NX_VEGA_REST` is the REST endpoint for the environment.
For examples, see Block Explorer's .env files [here](../../apps/explorer)
## Running unit tests
Run `nx test network-switcher` to execute the unit tests via [Jest](https://jestjs.io).

Some files were not shown because too many files have changed in this diff Show More