diff --git a/.github/workflows/network-explorer.yml b/.github/workflows/network-explorer.yml index 1dd2c89637..7577cca0b4 100644 --- a/.github/workflows/network-explorer.yml +++ b/.github/workflows/network-explorer.yml @@ -22,6 +22,8 @@ jobs: node-version: '14' - run: npm install continue-on-error: true + - name: Set environment from the example + run: cp .env.prod .env - run: npm run test continue-on-error: true - run: npm run build diff --git a/Cargo.lock b/Cargo.lock index 1f7669f5bd..11f22a91ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3910,10 +3910,11 @@ dependencies = [ [[package]] name = "okapi" -version = "0.6.0-alpha-1" +version = "0.7.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb085e00daf8d75b9dbf0ffdb4738e69503e28898d9641fa8bdc6ad536c7bcf4" +checksum = "ce66b6366e049880a35c378123fddb630b1a1a3c37fa1ca70caaf4a09f6e2893" dependencies = [ + "log", "schemars", "serde", "serde_json", @@ -5150,10 +5151,12 @@ dependencies = [ [[package]] name = "rocket_okapi" -version = "0.7.0-alpha-1" +version = "0.8.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2f4f48fb070f9f6c56d5663df5fa8a514406207744f4abd84661bfb24efd7d" +checksum = "0025aa04994af8cd8e1fcdd5a73579a395c941ae090ecb0a39b41cca7e237a20" dependencies = [ + "either", + "log", "okapi", "rocket", "rocket_okapi_codegen", @@ -5164,9 +5167,9 @@ dependencies = [ [[package]] name = "rocket_okapi_codegen" -version = "0.7.0-alpha-1" +version = "0.8.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ccf1550e1c806461a6b08e2ab64eb10701d41bf50bde59ab9aa3a57ab14d41" +checksum = "dc114779fc27afb78179233e966f469e47fd7a98dc15181cff2574cdddb65612" dependencies = [ "darling 0.13.0", "proc-macro2", diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index f9c043bae4..3719d89d7a 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -16,8 +16,8 @@ serde_json = "1.0.66" tokio = {version = "1.9.0", features = ["full"] } chrono = { version = "0.4.19", features = ["serde"] } schemars = { version = "0.8", features = ["preserve_order"] } -okapi = { version = "0.6.0-alpha-1", features = ["derive_json_schema"] } -rocket_okapi = "0.7.0-alpha-1" +okapi = { version = "0.7.0-rc.1", features = ["impl_json_schema"] } +rocket_okapi = { version = "0.8.0-rc.1", features = ["swagger"] } log = "0.4.0" pretty_env_logger = "0.4.0" thiserror = "1.0.29" diff --git a/explorer-api/src/country_statistics/geolocate.rs b/explorer-api/src/country_statistics/geolocate.rs index f03fd8d365..a09231ceae 100644 --- a/explorer-api/src/country_statistics/geolocate.rs +++ b/explorer-api/src/country_statistics/geolocate.rs @@ -15,6 +15,13 @@ impl GeoLocateTask { } pub(crate) fn start(mut self) { + if ::std::env::var("GEO_IP_SERVICE_API_KEY").is_err() { + error!( + "Env var GEO_IP_SERVICE_API_KEY is not set. Geolocation tasks will not be started." + ); + return; + } + info!("Spawning mix node locator task runner..."); tokio::spawn(async move { let mut interval_timer = tokio::time::interval(std::time::Duration::from_millis(50)); diff --git a/explorer-api/src/country_statistics/http.rs b/explorer-api/src/country_statistics/http.rs index 74be62cb13..45259f11dc 100644 --- a/explorer-api/src/country_statistics/http.rs +++ b/explorer-api/src/country_statistics/http.rs @@ -2,9 +2,12 @@ use crate::country_statistics::country_nodes_distribution::CountryNodesDistribut use crate::state::ExplorerApiStateContext; use rocket::serde::json::Json; use rocket::{Route, State}; +use rocket_okapi::okapi::openapi3::OpenApi; +use rocket_okapi::openapi_get_routes_spec; +use rocket_okapi::settings::OpenApiSettings; -pub fn country_statistics_make_default_routes() -> Vec { - routes_with_openapi![index] +pub fn country_statistics_make_default_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { + openapi_get_routes_spec![settings: index] } // We could either separate stuff by structure (like this, http is separate), or we could just diff --git a/explorer-api/src/http/mod.rs b/explorer-api/src/http/mod.rs index 2a3171081d..0cb78e6b4e 100644 --- a/explorer-api/src/http/mod.rs +++ b/explorer-api/src/http/mod.rs @@ -1,12 +1,13 @@ use log::info; use rocket::http::Method; -use rocket::Request; +use rocket::{Build, Request, Rocket}; use rocket_cors::{AllowedHeaders, AllowedOrigins}; use rocket_okapi::swagger_ui::make_swagger_ui; use crate::country_statistics::http::country_statistics_make_default_routes; use crate::http::swagger::get_docs; use crate::mix_node::http::mix_node_make_default_routes; +use crate::mix_nodes::http::mix_nodes_make_default_routes; use crate::ping::http::ping_make_default_routes; use crate::state::ExplorerApiStateContext; @@ -15,35 +16,45 @@ mod swagger; pub(crate) fn start(state: ExplorerApiStateContext) { tokio::spawn(async move { info!("Starting up..."); - - let allowed_origins = AllowedOrigins::all(); - - // You can also deserialize this - let cors = rocket_cors::CorsOptions { - allowed_origins, - allowed_methods: vec![Method::Get].into_iter().map(From::from).collect(), - allowed_headers: AllowedHeaders::some(&["*"]), - allow_credentials: true, - ..Default::default() - } - .to_cors() - .unwrap(); - - let config = rocket::config::Config::release_default(); - rocket::build() - .configure(config) - .mount("/countries", country_statistics_make_default_routes()) - .mount("/ping", ping_make_default_routes()) - .mount("/mix-node", mix_node_make_default_routes()) - .mount("/swagger", make_swagger_ui(&get_docs())) - .register("/", catchers![not_found]) - .manage(state) - .attach(cors) - .launch() - .await + configure_rocket(state).launch().await }); } +fn configure_rocket(state: ExplorerApiStateContext) -> Rocket { + let allowed_origins = AllowedOrigins::all(); + + // You can also deserialize this + let cors = rocket_cors::CorsOptions { + allowed_origins, + allowed_methods: vec![Method::Get].into_iter().map(From::from).collect(), + allowed_headers: AllowedHeaders::some(&["*"]), + allow_credentials: true, + ..Default::default() + } + .to_cors() + .unwrap(); + + let openapi_settings = rocket_okapi::settings::OpenApiSettings::default(); + let config = rocket::config::Config::release_default(); + let mut building_rocket = rocket::build().configure(config); + + mount_endpoints_and_merged_docs! { + building_rocket, + "/v1".to_owned(), + openapi_settings, + "/ping" => ping_make_default_routes(&openapi_settings), + "/countries" => country_statistics_make_default_routes(&openapi_settings), + "/mix-node" => mix_node_make_default_routes(&openapi_settings), + "/mix-nodes" => mix_nodes_make_default_routes(&openapi_settings), + }; + + building_rocket + .mount("/swagger", make_swagger_ui(&get_docs())) + .register("/", catchers![not_found]) + .manage(state) + .attach(cors) +} + #[catch(404)] pub(crate) fn not_found(req: &Request) -> String { format!("I couldn't find '{}'. Try something else?", req.uri()) diff --git a/explorer-api/src/http/swagger.rs b/explorer-api/src/http/swagger.rs index 4f30b69e74..c7489cfcaa 100644 --- a/explorer-api/src/http/swagger.rs +++ b/explorer-api/src/http/swagger.rs @@ -1,12 +1,8 @@ -use rocket_okapi::swagger_ui::{SwaggerUIConfig, UrlObject}; +use rocket_okapi::swagger_ui::SwaggerUIConfig; pub(crate) fn get_docs() -> SwaggerUIConfig { SwaggerUIConfig { - urls: vec![ - UrlObject::new("Country statistics", "/countries/openapi.json"), - UrlObject::new("Node ping", "/ping/openapi.json"), - UrlObject::new("Mix node", "/mix-node/openapi.json"), - ], + url: "../v1/openapi.json".to_owned(), ..Default::default() } } diff --git a/explorer-api/src/main.rs b/explorer-api/src/main.rs index b47e2e38c3..6aa9619b06 100644 --- a/explorer-api/src/main.rs +++ b/explorer-api/src/main.rs @@ -35,6 +35,11 @@ impl ExplorerApi { async fn run(&mut self) { info!("Explorer API starting up..."); + info!( + "Using validator API - {}", + network_defaults::default_api_endpoints()[0].clone() + ); + // spawn concurrent tasks mix_nodes::tasks::MixNodesTasks::new(self.state.clone()).start(); country_statistics::distribution::CountryStatisticsDistributionTask::new( diff --git a/explorer-api/src/mix_node/http.rs b/explorer-api/src/mix_node/http.rs index da6ec972a7..79b2b5750e 100644 --- a/explorer-api/src/mix_node/http.rs +++ b/explorer-api/src/mix_node/http.rs @@ -1,6 +1,9 @@ use reqwest::Error as ReqwestError; use rocket::serde::json::Json; use rocket::{Route, State}; +use rocket_okapi::okapi::openapi3::OpenApi; +use rocket_okapi::openapi_get_routes_spec; +use rocket_okapi::settings::OpenApiSettings; use serde::Serialize; use mixnet_contract::{Addr, Coin, Delegation, Layer, MixNode}; @@ -9,13 +12,12 @@ use crate::mix_node::models::{NodeDescription, NodeStats}; use crate::mix_nodes::{get_mixnode_delegations, get_single_mixnode_delegations, Location}; use crate::state::ExplorerApiStateContext; -pub fn mix_node_make_default_routes() -> Vec { - routes_with_openapi![ - get_delegations, +pub fn mix_node_make_default_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { + openapi_get_routes_spec![ + settings: get_delegations, get_all_delegations, get_description, get_stats, - list ] } @@ -29,14 +31,6 @@ pub(crate) struct PrettyMixNodeBondWithLocation { pub mix_node: MixNode, } -#[openapi(tag = "mix_node")] -#[get("/")] -pub(crate) async fn list( - state: &State, -) -> Json> { - Json(state.inner.mix_nodes.get_mixnodes_with_location().await) -} - #[openapi(tag = "mix_node")] #[get("//delegations")] pub(crate) async fn get_delegations(pubkey: &str) -> Json> { diff --git a/explorer-api/src/mix_nodes/http.rs b/explorer-api/src/mix_nodes/http.rs new file mode 100644 index 0000000000..efe75f6221 --- /dev/null +++ b/explorer-api/src/mix_nodes/http.rs @@ -0,0 +1,20 @@ +use rocket::serde::json::Json; +use rocket::{Route, State}; +use rocket_okapi::okapi::openapi3::OpenApi; +use rocket_okapi::openapi_get_routes_spec; +use rocket_okapi::settings::OpenApiSettings; + +use crate::mix_node::http::PrettyMixNodeBondWithLocation; +use crate::state::ExplorerApiStateContext; + +pub fn mix_nodes_make_default_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { + openapi_get_routes_spec![settings: list] +} + +#[openapi(tag = "mix_nodes")] +#[get("/")] +pub(crate) async fn list( + state: &State, +) -> Json> { + Json(state.inner.mix_nodes.get_mixnodes_with_location().await) +} diff --git a/explorer-api/src/mix_nodes/mod.rs b/explorer-api/src/mix_nodes/mod.rs index eaaa2095f2..c6c2a72f16 100644 --- a/explorer-api/src/mix_nodes/mod.rs +++ b/explorer-api/src/mix_nodes/mod.rs @@ -1,3 +1,4 @@ +pub(crate) mod http; pub(crate) mod tasks; mod utils; diff --git a/explorer-api/src/ping/http.rs b/explorer-api/src/ping/http.rs index 8853fbac61..006938bec6 100644 --- a/explorer-api/src/ping/http.rs +++ b/explorer-api/src/ping/http.rs @@ -4,6 +4,9 @@ use std::time::Duration; use rocket::serde::json::Json; use rocket::{Route, State}; +use rocket_okapi::okapi::openapi3::OpenApi; +use rocket_okapi::openapi_get_routes_spec; +use rocket_okapi::settings::OpenApiSettings; use mixnet_contract::MixNodeBond; @@ -12,8 +15,8 @@ use crate::state::ExplorerApiStateContext; const CONNECTION_TIMEOUT_SECONDS: Duration = Duration::from_secs(10); -pub fn ping_make_default_routes() -> Vec { - routes_with_openapi![index] +pub fn ping_make_default_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { + openapi_get_routes_spec![settings: index] } #[openapi(tag = "ping")] diff --git a/explorer/.env.prod b/explorer/.env.prod new file mode 100644 index 0000000000..17c51ea14e --- /dev/null +++ b/explorer/.env.prod @@ -0,0 +1,5 @@ +EXPLORER_API_URL=https://sandbox-explorer.nymtech.net/api/v1 +VALIDATOR_API_URL=https://sandbox-validator.nymtech.net +BIG_DIPPER_URL=https://sandbox-blocks.nymtech.net +CURRENCY_DENOM=unymt +CURRENCY_STAKING_DENOM=unyxt diff --git a/explorer/package-lock.json b/explorer/package-lock.json index 058a29c8b3..b1c9eee465 100644 --- a/explorer/package-lock.json +++ b/explorer/package-lock.json @@ -50,6 +50,7 @@ "clean-webpack-plugin": "^4.0.0", "css-loader": "^6.2.0", "css-minimizer-webpack-plugin": "^3.0.2", + "dotenv-webpack": "^7.0.3", "eslint": "7.32.0", "eslint-config-airbnb": "18.2.1", "eslint-config-prettier": "8.3.0", @@ -6198,6 +6199,39 @@ "tslib": "^2.0.3" } }, + "node_modules/dotenv-defaults": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dotenv-defaults/-/dotenv-defaults-2.0.2.tgz", + "integrity": "sha512-iOIzovWfsUHU91L5i8bJce3NYK5JXeAwH50Jh6+ARUdLiiGlYWfGw6UkzsYqaXZH/hjE/eCd/PlfM/qqyK0AMg==", + "dev": true, + "dependencies": { + "dotenv": "^8.2.0" + } + }, + "node_modules/dotenv-defaults/node_modules/dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-webpack": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/dotenv-webpack/-/dotenv-webpack-7.0.3.tgz", + "integrity": "sha512-O0O9pOEwrk+n1zzR3T2uuXRlw64QxHSPeNN1GaiNBloQFNaCUL9V8jxSVz4jlXXFP/CIqK8YecWf8BAvsSgMjw==", + "dev": true, + "dependencies": { + "dotenv-defaults": "^2.0.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "webpack": "^4 || ^5" + } + }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -21882,6 +21916,32 @@ "tslib": "^2.0.3" } }, + "dotenv-defaults": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dotenv-defaults/-/dotenv-defaults-2.0.2.tgz", + "integrity": "sha512-iOIzovWfsUHU91L5i8bJce3NYK5JXeAwH50Jh6+ARUdLiiGlYWfGw6UkzsYqaXZH/hjE/eCd/PlfM/qqyK0AMg==", + "dev": true, + "requires": { + "dotenv": "^8.2.0" + }, + "dependencies": { + "dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "dev": true + } + } + }, + "dotenv-webpack": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/dotenv-webpack/-/dotenv-webpack-7.0.3.tgz", + "integrity": "sha512-O0O9pOEwrk+n1zzR3T2uuXRlw64QxHSPeNN1GaiNBloQFNaCUL9V8jxSVz4jlXXFP/CIqK8YecWf8BAvsSgMjw==", + "dev": true, + "requires": { + "dotenv-defaults": "^2.0.2" + } + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", diff --git a/explorer/package.json b/explorer/package.json index 5d296ce0f6..553adb4ecb 100644 --- a/explorer/package.json +++ b/explorer/package.json @@ -45,6 +45,7 @@ "clean-webpack-plugin": "^4.0.0", "css-loader": "^6.2.0", "css-minimizer-webpack-plugin": "^3.0.2", + "dotenv-webpack": "^7.0.3", "eslint": "7.32.0", "eslint-config-airbnb": "18.2.1", "eslint-config-prettier": "8.3.0", diff --git a/explorer/src/api/constants.ts b/explorer/src/api/constants.ts index 88037559ce..99f749f927 100644 --- a/explorer/src/api/constants.ts +++ b/explorer/src/api/constants.ts @@ -1,17 +1,17 @@ // master APIs -export const MASTER_URL = 'https://testnet-milhon-explorer.nymtech.net'; -export const MASTER_VALIDATOR_URL = - 'https://testnet-milhon-validator1.nymtech.net'; -export const BIG_DIPPER = 'https://testnet-milhon-blocks.nymtech.net'; +export const API_BASE_URL = process.env.EXPLORER_API_URL; +export const VALIDATOR_API_BASE_URL = process.env.VALIDATOR_API_URL; +export const BIG_DIPPER = process.env.BIG_DIPPER_URL; // specific API routes -export const MIXNODE_PING = `${MASTER_URL}/api/ping`; -export const MIXNODES_API = `${MASTER_URL}/api/mix-node`; -export const GATEWAYS_API = `${MASTER_VALIDATOR_URL}/api/v1/gateways`; -export const VALIDATORS_API = `${MASTER_VALIDATOR_URL}/validators`; -export const BLOCK_API = `${MASTER_VALIDATOR_URL}/block`; -export const COUNTRY_DATA_API = `${MASTER_URL}/api/countries`; -export const UPTIME_STORY_API = `${MASTER_VALIDATOR_URL}/api/v1/status/mixnode`; // add ID then '/history' to this. +export const MIXNODE_PING = `${API_BASE_URL}/ping`; +export const MIXNODES_API = `${API_BASE_URL}/mix-nodes`; +export const MIXNODE_API = `${API_BASE_URL}/mix-node`; +export const GATEWAYS_API = `${VALIDATOR_API_BASE_URL}/api/v1/gateways`; +export const VALIDATORS_API = `${VALIDATOR_API_BASE_URL}/validators`; +export const BLOCK_API = `${VALIDATOR_API_BASE_URL}/block`; +export const COUNTRY_DATA_API = `${API_BASE_URL}/countries`; +export const UPTIME_STORY_API = `${VALIDATOR_API_BASE_URL}/api/v1/status/mixnode`; // add ID then '/history' to this. // errors export const MIXNODE_API_ERROR = diff --git a/explorer/src/api/index.ts b/explorer/src/api/index.ts index 473275b43d..f2647e2cc5 100644 --- a/explorer/src/api/index.ts +++ b/explorer/src/api/index.ts @@ -6,6 +6,7 @@ import { COUNTRY_DATA_API, MIXNODE_PING, UPTIME_STORY_API, + MIXNODE_API, } from './constants'; import { @@ -87,10 +88,10 @@ export class Api { static fetchDelegationsById = async ( id: string, ): Promise => - (await fetch(`${MIXNODES_API}/${id}/delegations`)).json(); + (await fetch(`${MIXNODE_API}/${id}/delegations`)).json(); static fetchStatsById = async (id: string): Promise => - (await fetch(`${MIXNODES_API}/${id}/stats`)).json(); + (await fetch(`${MIXNODE_API}/${id}/stats`)).json(); static fetchStatusById = async (id: string): Promise => (await fetch(`${MIXNODE_PING}/${id}`)).json(); diff --git a/explorer/src/components/BondBreakdown.tsx b/explorer/src/components/BondBreakdown.tsx index a78e452abc..d655e2b855 100644 --- a/explorer/src/components/BondBreakdown.tsx +++ b/explorer/src/components/BondBreakdown.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; -import { printableCoin } from '@nymproject/nym-validator-client'; -import { Alert, CircularProgress, useMediaQuery, Box } from '@mui/material'; +import { Alert, Box, CircularProgress, useMediaQuery } from '@mui/material'; import { useTheme } from '@mui/material/styles'; import Table from '@mui/material/Table'; import TableBody from '@mui/material/TableBody'; @@ -11,6 +10,7 @@ import TableRow from '@mui/material/TableRow'; import Paper from '@mui/material/Paper'; import { useMainContext } from 'src/context/main'; import { ExpandMore } from '@mui/icons-material'; +import { currencyToString } from '../utils/currency'; export const BondBreakdownTable: React.FC = () => { const { mixnodeDetailInfo, delegations } = useMainContext(); @@ -34,24 +34,23 @@ export const BondBreakdownTable: React.FC = () => { const thisMixnode = mixnodeDetailInfo?.data[0]; // delegations - const decimalisedDelegations = printableCoin({ - amount: thisMixnode.total_delegation.amount.toString(), - denom: thisMixnode.total_delegation.denom, - }); + const decimalisedDelegations = currencyToString( + thisMixnode.total_delegation.amount.toString(), + thisMixnode.total_delegation.denom, + ); // pledges - const decimalisedPledges = printableCoin({ - amount: thisMixnode.bond_amount.amount.toString(), - denom: thisMixnode.bond_amount.denom, - }); + const decimalisedPledges = currencyToString( + thisMixnode.pledge_amount.amount.toString(), + thisMixnode.pledge_amount.denom, + ); // bonds total (del + pledges) - const pledgesSum = Number(thisMixnode.bond_amount.amount); + const pledgesSum = Number(thisMixnode.pledge_amount.amount); const delegationsSum = Number(thisMixnode.total_delegation.amount); - const bondsTotal = printableCoin({ - amount: (delegationsSum + pledgesSum).toString(), - denom: 'upunk', - }); + const bondsTotal = currencyToString( + (delegationsSum + pledgesSum).toString(), + ); setBonds({ delegations: decimalisedDelegations, @@ -89,7 +88,7 @@ export const BondBreakdownTable: React.FC = () => { mixnodeDetailInfo.data[0].total_delegation.amount, ); const rawPledgeAmount = Number( - mixnodeDetailInfo.data[0].bond_amount.amount, + mixnodeDetailInfo.data[0].pledge_amount.amount, ); const rawTotalBondsAmount = rawDelegationAmount + rawPledgeAmount; return ((num * 100) / rawTotalBondsAmount).toFixed(1); @@ -203,7 +202,7 @@ export const BondBreakdownTable: React.FC = () => { {owner} - {printableCoin({ amount: amount.toString(), denom })} + {currencyToString(amount.toString(), denom)} {calcBondPercentage(amount)}% diff --git a/explorer/src/components/DetailTable.tsx b/explorer/src/components/DetailTable.tsx index 66a14611a6..dd3223ddd9 100644 --- a/explorer/src/components/DetailTable.tsx +++ b/explorer/src/components/DetailTable.tsx @@ -8,9 +8,9 @@ import { TableHead, TableRow, } from '@mui/material'; -import { printableCoin } from '@nymproject/nym-validator-client'; import { cellStyles } from './Universal-DataGrid'; import { MixnodeRowType } from '../utils/index'; +import { currencyToString } from '../utils/currency'; export type ColumnsType = { field: string; @@ -28,7 +28,7 @@ export interface UniversalTableProps { function formatCellValues(val: string | number, field: string) { if (field === 'bond') { - return printableCoin({ amount: val.toString(), denom: 'upunk' }); + return currencyToString(val.toString()); } return val; } diff --git a/explorer/src/pages/Gateways/index.tsx b/explorer/src/pages/Gateways/index.tsx index 6a91912cf1..a3ea58587d 100644 --- a/explorer/src/pages/Gateways/index.tsx +++ b/explorer/src/pages/Gateways/index.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import { Button, Card, Grid, Typography } from '@mui/material'; import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid'; -import { printableCoin } from '@nymproject/nym-validator-client'; import { SelectChangeEvent } from '@mui/material/Select'; import { useMainContext } from 'src/context/main'; import { gatewayToGridRow } from 'src/utils'; @@ -13,6 +12,7 @@ import { cellStyles, UniversalDataGrid, } from 'src/components/Universal-DataGrid'; +import { currencyToString } from '../../utils/currency'; export const PageGateways: React.FC = () => { const { gateways } = useMainContext(); @@ -79,17 +79,11 @@ export const PageGateways: React.FC = () => { renderHeader: () => , headerClassName: 'MuiDataGrid-header-override', headerAlign: 'left', - renderCell: (params: GridRenderCellParams) => { - const bondAsPunk = printableCoin({ - amount: params.value as string, - denom: 'upunk', - }); - return ( - - {bondAsPunk} - - ); - }, + renderCell: (params: GridRenderCellParams) => ( + + {currencyToString(params.value)} + + ), }, { field: 'host', diff --git a/explorer/src/pages/Mixnodes/index.tsx b/explorer/src/pages/Mixnodes/index.tsx index cdd3013b1d..ea52332344 100644 --- a/explorer/src/pages/Mixnodes/index.tsx +++ b/explorer/src/pages/Mixnodes/index.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid'; -import { printableCoin } from '@nymproject/nym-validator-client'; import { Button, Grid, Link as MuiLink, Card } from '@mui/material'; import { Link as RRDLink } from 'react-router-dom'; import { SelectChangeEvent } from '@mui/material/Select'; @@ -15,6 +14,7 @@ import { UniversalDataGrid, cellStyles, } from 'src/components/Universal-DataGrid'; +import { currencyToString } from '../../utils/currency'; export const PageMixnodes: React.FC = () => { const { mixnodes } = useMainContext(); @@ -92,21 +92,15 @@ export const PageMixnodes: React.FC = () => { headerClassName: 'MuiDataGrid-header-override', width: 150, headerAlign: 'left', - renderCell: (params: GridRenderCellParams) => { - const bondAsPunk = printableCoin({ - amount: params.value as string, - denom: 'upunk', - }); - return ( - - {bondAsPunk} - - ); - }, + renderCell: (params: GridRenderCellParams) => ( + + {currencyToString(params.value)} + + ), }, { field: 'location', diff --git a/explorer/src/typeDefs/explorer-api.ts b/explorer/src/typeDefs/explorer-api.ts index 0272d9a13a..5d1a9319b0 100644 --- a/explorer/src/typeDefs/explorer-api.ts +++ b/explorer/src/typeDefs/explorer-api.ts @@ -7,13 +7,14 @@ export interface ClientConfig { export interface MixNode { host: string; - location: string; + mix_port: number; + http_api_port: number; + verloc_port: number; sphinx_key: string; identity_key: string; version: string; - mix_port: number; - verloc_port: number; - http_api_port: number; + profit_margin_percent: number; + location: string; } export interface Gateway { @@ -32,7 +33,7 @@ export interface Amount { } export interface MixNodeResponseItem { - bond_amount: Amount; + pledge_amount: Amount; total_delegation: Amount; owner: string; layer: string; @@ -74,7 +75,7 @@ export type MixNodeHistoryResponse = StatsResponse; export interface GatewayResponseItem { block_height: number; - bond_amount: Amount; + pledge_amount: Amount; total_delegation: Amount; owner: string; gateway: Gateway; diff --git a/explorer/src/utils/currency.ts b/explorer/src/utils/currency.ts new file mode 100644 index 0000000000..c3c8d0c952 --- /dev/null +++ b/explorer/src/utils/currency.ts @@ -0,0 +1,19 @@ +import { printableCoin } from '@nymproject/nym-validator-client'; + +const DENOM = process.env.CURRENCY_DENOM || 'unym'; +const DENOM_STAKING = process.env.CURRENCY_STAKING_DENOM || 'unyx'; + +export const currencyToString = (amount: string, denom: string = DENOM) => + printableCoin({ + amount, + denom, + }); + +export const stakingCurrencyToString = ( + amount: string, + denom: string = DENOM_STAKING, +) => + printableCoin({ + amount, + denom, + }); diff --git a/explorer/src/utils/index.ts b/explorer/src/utils/index.ts index 7d0275798d..541ecd28ed 100644 --- a/explorer/src/utils/index.ts +++ b/explorer/src/utils/index.ts @@ -69,7 +69,7 @@ export function mixnodeToGridRow(arrayOfMixnodes: MixNodeResponse): any { return !arrayOfMixnodes ? [] : arrayOfMixnodes.map((mn) => { - const pledge = Number(mn.bond_amount.amount) || 0; + const pledge = Number(mn.pledge_amount.amount) || 0; const delegations = Number(mn.total_delegation.amount) || 0; const totalBond = pledge + delegations; const selfPercentage = ((pledge * 100) / totalBond).toFixed(2); @@ -96,7 +96,7 @@ export function gatewayToGridRow( owner: gw.owner, identity_key: gw.gateway.identity_key || '', location: gw?.gateway?.location || '', - bond: gw.bond_amount.amount || 0, + bond: gw.pledge_amount.amount || 0, host: gw.gateway.host || '', })); } diff --git a/explorer/webpack.common.js b/explorer/webpack.common.js index 9b84f6170c..894bc708cc 100644 --- a/explorer/webpack.common.js +++ b/explorer/webpack.common.js @@ -5,6 +5,7 @@ const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); const WebpackFavicons = require('webpack-favicons'); +const Dotenv = require('dotenv-webpack'); module.exports = { entry: './src/index.tsx', @@ -78,6 +79,8 @@ module.exports = { new WebpackFavicons({ src: 'src/logo.svg', }), + + new Dotenv(), ], output: { path: path.resolve(__dirname, 'dist'),