-
- {
- // @ts-ignore - Work around for complex type assignment issues
-
- }
-
- {t('validators_found')}:
-
- {localizeNumber(validatorCount, language)}
- {unlCount !== 0 && (
-
- {' '}
- ({t('unl')}: {unlCount})
-
- )}
-
-
+
{t('upgrade_status')}
-
{Object.keys(validatorAggregation).length > 0 ||
Object.keys(nodeAggregation).length > 0 ? (
diff --git a/src/containers/Network/Validators.tsx b/src/containers/Network/Validators.tsx
index 0f0ae5d8c..16590f02b 100644
--- a/src/containers/Network/Validators.tsx
+++ b/src/containers/Network/Validators.tsx
@@ -2,20 +2,31 @@ import { useContext, useState } from 'react'
import axios from 'axios'
import { useTranslation } from 'react-i18next'
import { useQuery } from 'react-query'
-import NetworkTabs from './NetworkTabs'
+import { Helmet } from 'react-helmet-async'
import Streams from '../shared/components/Streams'
-import ValidatorsTable from './ValidatorsTable'
+import { ValidatorsTable } from './ValidatorsTable'
import Log from '../shared/log'
import {
localizeNumber,
FETCH_INTERVAL_MILLIS,
FETCH_INTERVAL_ERROR_MILLIS,
+ FETCH_INTERVAL_FEE_SETTINGS_MILLIS,
} from '../shared/utils'
import { useLanguage } from '../shared/hooks'
import { Hexagons } from './Hexagons'
-import { StreamValidator, ValidatorResponse } from '../shared/vhsTypes'
+import {
+ FeeSettings,
+ StreamValidator,
+ ValidatorResponse,
+} from '../shared/vhsTypes'
import NetworkContext from '../shared/NetworkContext'
import { TooltipProvider } from '../shared/components/Tooltip'
+import './css/style.scss'
+import { VALIDATORS_ROUTE } from '../App/routes'
+import { useRouteParams } from '../shared/routing'
+import ValidatorsTabs from './ValidatorsTabs'
+import SocketContext from '../shared/SocketContext'
+import { getServerState } from '../../rippled/lib/rippled'
export const Validators = () => {
const language = useLanguage()
@@ -24,7 +35,12 @@ export const Validators = () => {
const [validations, setValidations] = useState([])
const [metrics, setMetrics] = useState({})
const [unlCount, setUnlCount] = useState(0)
+ const [feeSettings, setFeeSettings] = useState
(
+ undefined,
+ )
const network = useContext(NetworkContext)
+ const rippledSocket = useContext(SocketContext)
+ const { tab = 'uptime' } = useRouteParams(VALIDATORS_ROUTE)
useQuery(['fetchValidatorsData'], () => fetchData(), {
refetchInterval: (returnedData, _) =>
@@ -35,6 +51,15 @@ export const Validators = () => {
enabled: process.env.VITE_ENVIRONMENT !== 'custom' || !!network,
})
+ useQuery(['fetchFeeSettingsData'], () => fetchFeeSettingsData(), {
+ refetchInterval: (returnedData, _) =>
+ returnedData == null
+ ? FETCH_INTERVAL_ERROR_MILLIS
+ : FETCH_INTERVAL_FEE_SETTINGS_MILLIS,
+ refetchOnMount: true,
+ enabled: process.env.VITE_ENVIRONMENT !== 'custom' || !!network,
+ })
+
function mergeLatest(
validators: Record,
live: Record,
@@ -55,6 +80,20 @@ export const Validators = () => {
return updated
}
+ function fetchFeeSettingsData() {
+ if (tab === 'voting') {
+ getServerState(rippledSocket)
+ .then((res) => res.state)
+ .then((state) => {
+ setFeeSettings({
+ base_fee: state.validated_ledger.base_fee,
+ reserve_base: state.validated_ledger.reserve_base,
+ reserve_inc: state.validated_ledger.reserve_inc,
+ })
+ })
+ }
+ }
+
function fetchData() {
const url = `${process.env.VITE_DATA_URL}/validators/${network}`
@@ -92,8 +131,27 @@ export const Validators = () => {
}
const validatorCount = Object.keys(vList).length
+
+ const Body = {
+ uptime: (
+
+ ),
+ voting: (
+
+ ),
+ }[tab]
return (
+
{t('validators')}
{network && (
{
-
-
+
+
+ {Body}
)
diff --git a/src/containers/Network/ValidatorsTable.jsx b/src/containers/Network/ValidatorsTable.jsx
deleted file mode 100644
index 7649b8781..000000000
--- a/src/containers/Network/ValidatorsTable.jsx
+++ /dev/null
@@ -1,158 +0,0 @@
-import { Component } from 'react'
-import PropTypes from 'prop-types'
-import { withTranslation } from 'react-i18next'
-import { Loader } from '../shared/components/Loader'
-import SuccessIcon from '../shared/images/success.svg'
-import DomainLink from '../shared/components/DomainLink'
-import InfoIcon from '../shared/images/info.svg'
-import './css/validatorsTable.scss'
-import { RouteLink } from '../shared/routing'
-import { LEDGER_ROUTE, VALIDATOR_ROUTE } from '../App/routes'
-
-class ValidatorsTable extends Component {
- static getDerivedStateFromProps(nextProps, prevState) {
- return nextProps.validators
- ? {
- validators: ValidatorsTable.sortValidators(
- Object.values(nextProps.validators),
- ),
- metrics: nextProps.metrics,
- }
- : null
- }
-
- static sortValidators = (data) => {
- data.sort((a, b) => {
- const aUnl = a.unl || 'zzz'
- const bUnl = b.unl || 'zzz'
- const aDomain = a.domain || 'zzz'
- const bDomain = b.domain || 'zzz'
- const aScore = a.agreement_30day ? a.agreement_30day.score : -1
- const bScore = b.agreement_30day ? b.agreement_30day.score : -1
- const aPubkey = a.master_key || a.signing_key
- const bPubkey = b.master_key || b.signing_key
-
- // 1. Sort by whether the validator is on the UNL
- if (aUnl > bUnl) return 1
- if (aUnl < bUnl) return -1
- // 2. Sort by the 30 day score (descending)
- if (aScore < bScore) return 1
- if (aScore > bScore) return -1
- // 3. Sort alphabetically by the domain
- if (aDomain > bDomain) return 1
- if (aDomain < bDomain) return -1
- // 4. Sort alphabetically by the public key
- if (aPubkey > bPubkey) return 1
- if (aPubkey < bPubkey) return -1
-
- return 0
- })
-
- return data
- }
-
- constructor(props) {
- super(props)
- this.state = {}
- }
-
- static renderDomain = (domain) => domain &&
-
- renderAgreement = (className, d) => {
- const { t } = this.props
-
- return d ? (
-
- {Number.parseFloat(d.score).toFixed(5)}
- {d.incomplete && *}
- |
- ) : (
-
|
- )
- }
-
- renderValidator = (d) => {
- const { metrics } = this.state
- const color = d.ledger_hash ? `#${d.ledger_hash.substring(0, 6)}` : ''
- const trusted = d.unl ? 'yes' : 'no'
- const pubkey = d.master_key || d.signing_key
- const onNegativeUnl = metrics.nUnl && metrics.nUnl.includes(pubkey)
- const nUnl = onNegativeUnl ? 'yes' : 'no'
- const ledgerIndex = d.ledger_index ?? d.current_index
-
- return (
-
-
-
- {pubkey}
-
- |
-
- {ValidatorsTable.renderDomain(d.domain)}
- |
-
- {d.unl && }
- |
-
- {onNegativeUnl && }
- |
- {d.server_version} |
- {this.renderAgreement('h1', d.agreement_1h)}
- {this.renderAgreement('h24', d.agreement_24h)}
- {this.renderAgreement('d30', d.agreement_30day)}
-
-
- {ledgerIndex}
-
- {d.partial && '*'}
- |
-
- )
- }
-
- render() {
- const { t } = this.props
- const { validators } = this.state
- const content = validators ? (
-
-
-
- {t('pubkey')} |
- {t('domain')} |
- {t('unl')} |
- {t('nUnlCol')} |
- {t('Version')} |
- {t('1H')} |
- {t('24H')} |
- {t('30D')} |
- {t('ledger')} |
-
-
- {validators.map(this.renderValidator)}
-
- ) : (
-
- )
-
- return
{content}
- }
-}
-
-ValidatorsTable.propTypes = {
- validators: PropTypes.arrayOf(PropTypes.shape({})),
- t: PropTypes.func.isRequired,
- metrics: PropTypes.shape({}).isRequired,
-}
-
-ValidatorsTable.defaultProps = {
- validators: null,
-}
-
-export default withTranslation()(ValidatorsTable)
diff --git a/src/containers/Network/ValidatorsTable.tsx b/src/containers/Network/ValidatorsTable.tsx
new file mode 100644
index 000000000..60fe2715d
--- /dev/null
+++ b/src/containers/Network/ValidatorsTable.tsx
@@ -0,0 +1,196 @@
+import { useTranslation } from 'react-i18next'
+import { FeeSettings, StreamValidator } from '../shared/vhsTypes'
+import { RouteLink } from '../shared/routing'
+import { VALIDATOR_ROUTE, LEDGER_ROUTE } from '../App/routes'
+import SuccessIcon from '../shared/images/success.svg'
+import UpIcon from '../shared/images/ic_up.svg'
+import DownIcon from '../shared/images/ic_down.svg'
+import DomainLink from '../shared/components/DomainLink'
+import InfoIcon from '../shared/images/info.svg'
+import { Loader } from '../shared/components/Loader'
+import './css/validatorsTable.scss'
+import { useLanguage } from '../shared/hooks'
+import { renderXRP } from '../shared/utils'
+
+const DROPS_TO_XRP_FACTOR = 1000000
+
+interface ValidatorsTableProps {
+ validators: StreamValidator[]
+ metrics: any
+ tab: string
+ feeSettings?: FeeSettings
+}
+
+const sortValidators = (data) => {
+ data.sort((a, b) => {
+ const aUnl = a.unl || 'zzz'
+ const bUnl = b.unl || 'zzz'
+ const aDomain = a.domain || 'zzz'
+ const bDomain = b.domain || 'zzz'
+ const aScore = a.agreement_30day ? a.agreement_30day.score : -1
+ const bScore = b.agreement_30day ? b.agreement_30day.score : -1
+ const aPubkey = a.master_key || a.signing_key
+ const bPubkey = b.master_key || b.signing_key
+
+ // 1. Sort by whether the validator is on the UNL
+ if (aUnl > bUnl) return 1
+ if (aUnl < bUnl) return -1
+ // 2. Sort by the 30 day score (descending)
+ if (aScore < bScore) return 1
+ if (aScore > bScore) return -1
+ // 3. Sort alphabetically by the domain
+ if (aDomain > bDomain) return 1
+ if (aDomain < bDomain) return -1
+ // 4. Sort alphabetically by the public key
+ if (aPubkey > bPubkey) return 1
+ if (aPubkey < bPubkey) return -1
+
+ return 0
+ })
+
+ return data
+}
+
+export const ValidatorsTable = (props: ValidatorsTableProps) => {
+ const { validators: rawValidators, metrics, tab, feeSettings } = props
+ const validators = rawValidators ? sortValidators(rawValidators) : undefined
+ const { t } = useTranslation()
+ const language = useLanguage()
+
+ const renderDomain = (domain) => domain &&
+
+ const renderAgreement = (className, d) =>
+ d ? (
+
+ {Number.parseFloat(d.score).toFixed(5)}
+ {d.incomplete && *}
+ |
+ ) : (
+
|
+ )
+
+ const renderFeeVoting = (className, data, currentFee, pubkey) =>
+ data ? (
+
+ {currentFee &&
+ data !== currentFee &&
+ (data > currentFee ? (
+
+
+
+ ) : (
+
+
+
+ ))}
+ {renderXRP(data / DROPS_TO_XRP_FACTOR, language)}
+ |
+ ) : (
+
|
+ )
+
+ const renderValidator = (d) => {
+ const color = d.ledger_hash ? `#${d.ledger_hash.substring(0, 6)}` : ''
+ const trusted = d.unl ? 'yes' : 'no'
+ const pubkey = d.master_key || d.signing_key
+ const onNegativeUnl = metrics.nUnl && metrics.nUnl.includes(pubkey)
+ const nUnl = onNegativeUnl ? 'yes' : 'no'
+ const ledgerIndex = d.ledger_index ?? d.current_index
+
+ return (
+
+
+
+ {pubkey}
+
+ |
+ {renderDomain(d.domain)} |
+
+ {d.unl && }
+ |
+
+ {onNegativeUnl && }
+ |
+ {d.server_version} |
+ {tab === 'uptime' ? (
+ <>
+ {renderAgreement('h1', d.agreement_1h)}
+ {renderAgreement('h24', d.agreement_24h)}
+ {renderAgreement('d30', d.agreement_30day)}
+ >
+ ) : (
+ <>
+ {renderFeeVoting(
+ 'base',
+ d.reserve_base,
+ feeSettings?.reserve_base,
+ pubkey,
+ )}
+ {renderFeeVoting(
+ 'owner',
+ d.reserve_inc,
+ feeSettings?.reserve_inc,
+ pubkey,
+ )}
+ {renderFeeVoting(
+ 'base_fee',
+ d.base_fee,
+ feeSettings?.base_fee,
+ pubkey,
+ )}
+ >
+ )}
+
+
+
+ {ledgerIndex}
+
+ {d.partial && '*'}
+ |
+
+ )
+ }
+
+ const content = validators ? (
+
+
+
+ {t('pubkey')} |
+ {t('domain')} |
+ {t('unl')} |
+ {t('nUnlCol')} |
+ {t('Version')} |
+ {tab === 'uptime' ? (
+ <>
+ {t('1H')} |
+ {t('24H')} |
+ {t('30D')} |
+ >
+ ) : (
+ <>
+ {' '}
+
+ {t('base')}
+ |
+ {t('owner')} |
+ {t('base_fee')} |
+ >
+ )}
+ {t('ledger')} |
+
+
+ {validators.map(renderValidator)}
+
+ ) : (
+
+ )
+
+ return
{content}
+}
diff --git a/src/containers/Network/ValidatorsTabs.tsx b/src/containers/Network/ValidatorsTabs.tsx
new file mode 100644
index 000000000..bcd64fae2
--- /dev/null
+++ b/src/containers/Network/ValidatorsTabs.tsx
@@ -0,0 +1,21 @@
+import { Tabs } from '../shared/components/Tabs'
+import { buildPath } from '../shared/routing'
+import { VALIDATORS_ROUTE } from '../App/routes'
+
+interface Props {
+ selected: string
+}
+
+const ValidatorsTabs = (props: Props) => {
+ const { selected } = props
+ const tabs = ['uptime', 'voting']
+ return (
+
+ )
+}
+
+export default ValidatorsTabs
diff --git a/src/containers/Network/css/style.scss b/src/containers/Network/css/style.scss
index c2f1f30e0..0ad498ddf 100644
--- a/src/containers/Network/css/style.scss
+++ b/src/containers/Network/css/style.scss
@@ -39,4 +39,20 @@
max-width: 1500px;
margin: auto;
}
+
+ .type {
+ display: inline-block;
+ margin-top: 80px;
+ margin-bottom: 32px;
+ margin-left: 16px;
+ margin-left: calc((100vw - 1500px) / 2);
+ margin-left: clamp(16px, calc((100vw - 1500px) / 2), calc((100vw - 1500px) / 2)); // Adjust based on wrap margin with min 16px
+ color: $white;
+ font-size: 32px;
+ @include for-size(tablet-portrait-up) {
+ font-size: 42px;
+ }
+
+ @include bold;
+ }
}
diff --git a/src/containers/Network/css/validatorsTable.scss b/src/containers/Network/css/validatorsTable.scss
index 0b2aab55d..ed1775702 100644
--- a/src/containers/Network/css/validatorsTable.scss
+++ b/src/containers/Network/css/validatorsTable.scss
@@ -5,13 +5,6 @@
min-height: 150px;
table {
- .pubkey,
- .score.h1,
- .score.d30,
- .fee {
- display: none;
- }
-
.pubkey {
max-width: 70px;
@include for-size(tablet-portrait-up) {
@@ -83,6 +76,25 @@
color: $orange-40;
}
+ .fee-icon {
+ position: relative;
+ top: 1.5px;
+ margin-right: 4px;
+ }
+
+ .vote {
+ white-space: nowrap;
+ }
+ }
+
+ &.uptime-tab {
+ .pubkey,
+ .score.h1,
+ .score.d30,
+ .fee {
+ display: none;
+ }
+
@include for-size(tablet-portrait-up) {
.score.d30 {
display: table-cell;
@@ -102,4 +114,32 @@
}
}
}
+
+ &.voting-tab {
+ .pubkey,
+ .last-ledger,
+ .n-unl,
+ .version {
+ display: none;
+ }
+
+ @include for-size(tablet-portrait-up) {
+ .n-unl, .version {
+ display: table-cell;
+ }
+ }
+
+ @include for-size(tablet-landscape-up) {
+ .pubkey {
+ display: table-cell;
+ }
+ }
+
+ @include for-size(desktop-up) {
+ .last-ledger {
+ display: table-cell;
+ }
+ }
+ }
+
}
diff --git a/src/containers/Network/index.tsx b/src/containers/Network/index.tsx
deleted file mode 100644
index 70888bc9d..000000000
--- a/src/containers/Network/index.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import { useContext, useEffect } from 'react'
-import { useTranslation } from 'react-i18next'
-import { Helmet } from 'react-helmet-async'
-import { NETWORK_ROUTE } from '../App/routes'
-import { useAnalytics } from '../shared/analytics'
-import { useRouteParams } from '../shared/routing'
-import NetworkContext from '../shared/NetworkContext'
-import { Validators } from './Validators'
-import { UpgradeStatus } from './UpgradeStatus'
-import { Nodes } from './Nodes'
-import NoMatch from '../NoMatch'
-import './css/style.scss'
-
-export const Network = () => {
- const { trackScreenLoaded } = useAnalytics()
- const { t } = useTranslation()
- const { tab = 'nodes' } = useRouteParams(NETWORK_ROUTE)
- const network = useContext(NetworkContext)
-
- useEffect(() => {
- trackScreenLoaded()
- }, [tab, trackScreenLoaded])
-
- if (network === null) {
- return (
-
- )
- }
-
- const Body = {
- 'upgrade-status': UpgradeStatus,
- validators: Validators,
- nodes: Nodes,
- }[tab]
- return (
- <>
-
-
- >
- )
-}
diff --git a/src/containers/Network/test/mockValidators.json b/src/containers/Network/test/mockValidators.json
index d5c444fa3..e425574cb 100644
--- a/src/containers/Network/test/mockValidators.json
+++ b/src/containers/Network/test/mockValidators.json
@@ -22,7 +22,10 @@
"score": 0.98468,
"missed": 120,
"incomplete": true
- }
+ },
+ "base_fee":10,
+ "reserve_base":1000000,
+ "reserve_inc":200000
},
{
"master_key": "nHDaxUL87HiVszvCamVu4A3Gecq6LTxKkUVNdzf3nqmuSywgRqu4",
@@ -47,7 +50,10 @@
"score": 0.98,
"missed": 1,
"incomplete": true
- }
+ },
+ "base_fee": 200,
+ "reserve_base": 1000000,
+ "reserve_inc": 200000
},
{
"master_key": "nHUroc3Q1ErBXs689SEi3nWEeM759Pn1LsZk27jMHJtiemHXVmJb",
@@ -97,6 +103,9 @@
"score": 0.99964,
"missed": 15,
"incomplete": true
- }
+ },
+ "base_fee": 12,
+ "reserve_base": 1000000,
+ "reserve_inc": 100000
}
]
diff --git a/src/containers/Network/test/nodes.test.js b/src/containers/Network/test/nodes.test.js
index 7455912d5..e6bd6afd5 100644
--- a/src/containers/Network/test/nodes.test.js
+++ b/src/containers/Network/test/nodes.test.js
@@ -2,12 +2,12 @@ import { mount } from 'enzyme'
import moxios from 'moxios'
import { Route } from 'react-router-dom'
import i18n from '../../../i18n/testConfig'
-import { Network } from '../index'
import mockNodes from './mockNodes.json'
import NetworkContext from '../../shared/NetworkContext'
import countries from '../../../../public/countries.json'
import { QuickHarness } from '../../test/utils'
-import { NETWORK_ROUTE } from '../../App/routes'
+import { NODES_ROUTE } from '../../App/routes'
+import { Nodes } from '../Nodes'
jest.mock('usehooks-ts', () => ({
useWindowSize: () => ({
@@ -21,7 +21,7 @@ describe('Nodes Page container', () => {
mount(
- } />
+ } />
,
)
diff --git a/src/containers/Network/test/upgradeStatus.test.js b/src/containers/Network/test/upgradeStatus.test.js
index 28d43d480..0c5ad5ffb 100644
--- a/src/containers/Network/test/upgradeStatus.test.js
+++ b/src/containers/Network/test/upgradeStatus.test.js
@@ -3,16 +3,16 @@ import moxios from 'moxios'
import WS from 'jest-websocket-mock'
import { Route } from 'react-router'
import i18n from '../../../i18n/testConfig'
-import { Network } from '../index'
import SocketContext from '../../shared/SocketContext'
import MockWsClient from '../../test/mockWsClient'
import { QuickHarness } from '../../test/utils'
-import { NETWORK_ROUTE } from '../../App/routes'
import {
+ UpgradeStatus,
aggregateData,
aggregateNodes,
aggregateValidators,
} from '../UpgradeStatus'
+import { UPGRADE_STATUS_ROUTE } from '../../App/routes'
const undefinedValidatorsData = [
{
@@ -113,7 +113,7 @@ describe('UpgradeStatus renders', () => {
mount(
- } />
+ } />
,
)
diff --git a/src/containers/Network/test/validators.test.js b/src/containers/Network/test/validators.test.js
index e8523d7b3..5f6739702 100644
--- a/src/containers/Network/test/validators.test.js
+++ b/src/containers/Network/test/validators.test.js
@@ -3,13 +3,13 @@ import moxios from 'moxios'
import WS from 'jest-websocket-mock'
import { Route } from 'react-router'
import i18n from '../../../i18n/testConfig'
-import { Network } from '../index'
import mockValidators from './mockValidators.json'
import validationMessage from './mockValidation.json'
import SocketContext from '../../shared/SocketContext'
import MockWsClient from '../../test/mockWsClient'
import { QuickHarness } from '../../test/utils'
-import { NETWORK_ROUTE } from '../../App/routes'
+import { VALIDATORS_ROUTE } from '../../App/routes'
+import { Validators } from '../Validators'
const WS_URL = 'ws://localhost:1234'
@@ -20,7 +20,7 @@ describe('Validators Tab container', () => {
mount(
- } />
+ } />
,
)
diff --git a/src/containers/Network/test/validatorsTable.test.js b/src/containers/Network/test/validatorsTable.test.js
index 2bd4e69b5..93367ebf7 100644
--- a/src/containers/Network/test/validatorsTable.test.js
+++ b/src/containers/Network/test/validatorsTable.test.js
@@ -2,7 +2,7 @@ import { mount } from 'enzyme'
import { BrowserRouter as Router } from 'react-router-dom'
import { I18nextProvider } from 'react-i18next'
import i18n from '../../../i18n/testConfig'
-import ValidatorsTable from '../ValidatorsTable'
+import { ValidatorsTable } from '../ValidatorsTable'
import validators from './mockValidators.json'
import metrics from './metrics.json'
@@ -23,8 +23,29 @@ describe('Validators table', () => {
})
it('renders all parts', () => {
- const wrapper = createWrapper({ validators, metrics })
+ const tab = 'uptime'
+ const wrapper = createWrapper({ validators, metrics, tab })
expect(wrapper.find('tr').length).toBe(validators.length + 1)
wrapper.unmount()
})
+
+ it('renders uptime tab', () => {
+ const tab = 'uptime'
+ const wrapper = createWrapper({ validators, metrics, tab })
+ expect(wrapper.find('.uptime-tab').length).toBe(1)
+ expect(wrapper.find('td.h1').at(0).text().trim()).toBe('1.00000')
+ expect(wrapper.find('td.h24').at(0).text().trim()).toBe('0.91729*')
+ expect(wrapper.find('td.d30').at(0).text().trim()).toBe('0.98468*')
+ wrapper.unmount()
+ })
+
+ it('renders voting tab', () => {
+ const tab = 'voting'
+ const wrapper = createWrapper({ validators, metrics, tab })
+ expect(wrapper.find('.voting-tab').length).toBe(1)
+ expect(wrapper.find('td.base').at(0).text().trim()).toContain('1.00')
+ expect(wrapper.find('td.owner').at(0).text().trim()).toContain('0.20')
+ expect(wrapper.find('td.base_fee').at(0).text().trim()).toContain('0.00001')
+ wrapper.unmount()
+ })
})
diff --git a/src/containers/shared/css/tabs.scss b/src/containers/shared/css/tabs.scss
index 3ffa955ec..d78164f61 100644
--- a/src/containers/shared/css/tabs.scss
+++ b/src/containers/shared/css/tabs.scss
@@ -16,6 +16,7 @@
color: $black-40;
cursor: pointer;
text-align: center;
+ text-transform: capitalize;
&:hover,
&:focus {
diff --git a/src/containers/shared/images/ic_down.svg b/src/containers/shared/images/ic_down.svg
new file mode 100644
index 000000000..24d406e4a
--- /dev/null
+++ b/src/containers/shared/images/ic_down.svg
@@ -0,0 +1,19 @@
+
diff --git a/src/containers/shared/images/ic_up.svg b/src/containers/shared/images/ic_up.svg
new file mode 100644
index 000000000..1f8082b27
--- /dev/null
+++ b/src/containers/shared/images/ic_up.svg
@@ -0,0 +1,19 @@
+
diff --git a/src/containers/shared/utils.js b/src/containers/shared/utils.js
index 9580b4754..f2959e1e5 100644
--- a/src/containers/shared/utils.js
+++ b/src/containers/shared/utils.js
@@ -22,6 +22,7 @@ export const FETCH_INTERVAL_VHS_MILLIS = 60 * 1000 // 1 minute
export const FETCH_INTERVAL_NODES_MILLIS = 60000
export const FETCH_INTERVAL_ERROR_MILLIS = 300
export const FETCH_INTERVAL_XRP_USD_ORACLE_MILLIS = 60 * 1000
+export const FETCH_INTERVAL_FEE_SETTINGS_MILLIS = 10 * 60 * 1000 // 10 minutes
export const DECIMAL_REGEX = /^\d+$/
export const HASH256_REGEX = /[0-9A-Fa-f]{64}/i
diff --git a/src/containers/shared/vhsTypes.ts b/src/containers/shared/vhsTypes.ts
index 3984b56af..e2b37f0f4 100644
--- a/src/containers/shared/vhsTypes.ts
+++ b/src/containers/shared/vhsTypes.ts
@@ -101,6 +101,15 @@ export interface StreamValidator extends ValidatorResponse {
ledger_hash?: string
pubkey?: string
time?: string
+ base_fee?: number
+ reserve_base?: number
+ reserve_inc?: number
+}
+
+export interface FeeSettings {
+ base_fee: number
+ reserve_base: number
+ reserve_inc: number
}
export interface AmendmentData {
diff --git a/src/rippled/lib/rippled.js b/src/rippled/lib/rippled.js
index 85900ffa9..d4bc588bf 100644
--- a/src/rippled/lib/rippled.js
+++ b/src/rippled/lib/rippled.js
@@ -509,6 +509,17 @@ const getServerInfo = (rippledSocket) =>
return resp
})
+const getServerState = (rippledSocket) =>
+ query(rippledSocket, {
+ command: 'server_state',
+ }).then((resp) => {
+ if (resp.error !== undefined || resp.error_message !== undefined) {
+ throw new Error(resp.error_message || resp.error, 500)
+ }
+
+ return resp
+ })
+
const getOffers = (
rippledSocket,
currencyCode,
@@ -653,6 +664,7 @@ export {
getAccountTransactions,
getNegativeUNL,
getServerInfo,
+ getServerState,
getOffers,
getNFTInfo,
getBuyNFToffers,