diff --git a/.github/workflows/deploy-jungle-testnet.yaml b/.github/workflows/deploy-jungle-testnet.yaml
index 3d0068b9..57e94f75 100644
--- a/.github/workflows/deploy-jungle-testnet.yaml
+++ b/.github/workflows/deploy-jungle-testnet.yaml
@@ -102,6 +102,9 @@ jobs:
HAPI_EOS_EXCHANGE_RATE_API: https://api.coingecko.com/api/v3/simple/price?ids=eos&vs_currencies=usd
HAPI_COINGECKO_API_TOKEN_ID: eos
HAPI_REWARDS_TOKEN: EOS
+ HAPI_EOSRATE_GET_STATS_URL: ${{ secrets.HAPI_EOSRATE_GET_STATS_URL }}
+ HAPI_EOSRATE_GET_STATS_USER: ${{ secrets.HAPI_EOSRATE_GET_STATS_USER }}
+ HAPI_EOSRATE_GET_STATS_PASSWORD: ${{ secrets.HAPI_EOSRATE_GET_STATS_PASSWORD }}
# hasura
HASURA_GRAPHQL_ENABLE_CONSOLE: true
HASURA_GRAPHQL_DATABASE_URL: ${{ secrets.HASURA_GRAPHQL_DATABASE_URL }}
diff --git a/.github/workflows/deploy-mainnet.yaml b/.github/workflows/deploy-mainnet.yaml
index f7e15212..967a8b3d 100644
--- a/.github/workflows/deploy-mainnet.yaml
+++ b/.github/workflows/deploy-mainnet.yaml
@@ -100,6 +100,9 @@ jobs:
HAPI_REWARDS_TOKEN: EOS
HAPI_RE_CAPTCHA_PROJECT_ID: ${{ secrets.HAPI_RE_CAPTCHA_PROJECT_ID }}
HAPI_PUBLIC_RE_CAPTCHA_KEY: ${{ secrets.HAPI_PUBLIC_RE_CAPTCHA_KEY }}
+ HAPI_EOSRATE_GET_STATS_URL: ${{ secrets.HAPI_EOSRATE_GET_STATS_URL }}
+ HAPI_EOSRATE_GET_STATS_USER: ${{ secrets.HAPI_EOSRATE_GET_STATS_USER }}
+ HAPI_EOSRATE_GET_STATS_PASSWORD: ${{ secrets.HAPI_EOSRATE_GET_STATS_PASSWORD }}
# hasura
HASURA_GRAPHQL_ENABLE_CONSOLE: 'true'
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 81d68f9c..e2db648e 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -64,6 +64,9 @@ services:
HAPI_RE_CAPTCHA_PROJECT_ID: '${HAPI_RE_CAPTCHA_PROJECT_ID}'
HAPI_PUBLIC_RE_CAPTCHA_KEY: '${HAPI_PUBLIC_RE_CAPTCHA_KEY}'
HAPI_CREATE_ACCOUNT_ACTION_NAME: '${HAPI_CREATE_ACCOUNT_ACTION_NAME}'
+ HAPI_EOSRATE_GET_STATS_URL: '${HAPI_EOSRATE_GET_STATS_URL}'
+ HAPI_EOSRATE_GET_STATS_USER: '${HAPI_EOSRATE_GET_STATS_USER}'
+ HAPI_EOSRATE_GET_STATS_PASSWORD: '${HAPI_EOSRATE_GET_STATS_PASSWORD}'
hasura:
container_name: '${STAGE}-${APP_NAME}-hasura'
image: hasura/graphql-engine:v2.16.0.cli-migrations-v3
diff --git a/hapi/src/config/eos.config.js b/hapi/src/config/eos.config.js
index 738e0630..c35447db 100644
--- a/hapi/src/config/eos.config.js
+++ b/hapi/src/config/eos.config.js
@@ -1,15 +1,16 @@
module.exports = {
networkName: process.env.HAPI_EOS_API_NETWORK_NAME,
apiEndpoints: process.env.HAPI_EOS_API_ENDPOINTS
- ? JSON.parse(process.env.HAPI_EOS_API_ENDPOINTS)
- : [],
+ ? JSON.parse(process.env.HAPI_EOS_API_ENDPOINTS)
+ : [],
apiEndpoint: process.env.HAPI_EOS_API_ENDPOINTS
- ? JSON.parse(process.env.HAPI_EOS_API_ENDPOINTS)[0]
- : '',
+ ? JSON.parse(process.env.HAPI_EOS_API_ENDPOINTS)[0]
+ : '',
stateHistoryPluginEndpoint:
process.env.HAPI_EOS_STATE_HISTORY_PLUGIN_ENDPOINT,
chainId: process.env.HAPI_EOS_API_CHAIN_ID,
- eosChainId: 'aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906',
+ eosChainId:
+ 'aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906',
eosTopLimit: 150,
baseAccount: process.env.HAPI_EOS_BASE_ACCOUNT,
baseAccountPassword: process.env.HAPI_EOS_BASE_ACCOUNT_PASSWORD,
@@ -41,5 +42,8 @@ module.exports = {
knownNetworks: {
lacchain: 'lacchain'
},
- rewardsToken: process.env.HAPI_REWARDS_TOKEN
+ rewardsToken: process.env.HAPI_REWARDS_TOKEN,
+ eosRateUrl: process.env.HAPI_EOSRATE_GET_STATS_URL,
+ eosRateUser: process.env.HAPI_EOSRATE_GET_STATS_USER,
+ eosRatePassword: process.env.HAPI_EOSRATE_GET_STATS_PASSWORD
}
diff --git a/hapi/src/routes/get-eos-rate-stats.route.js b/hapi/src/routes/get-eos-rate-stats.route.js
new file mode 100644
index 00000000..dd0fa54c
--- /dev/null
+++ b/hapi/src/routes/get-eos-rate-stats.route.js
@@ -0,0 +1,35 @@
+const { eosConfig } = require('../config')
+const { axiosUtil } = require('../utils')
+
+module.exports = {
+ method: 'POST',
+ path: '/get-eos-rate',
+ handler: async () => {
+ if (
+ !eosConfig.eosRateUrl ||
+ !eosConfig.eosRateUser ||
+ !eosConfig.eosRatePassword
+ ) {
+ return []
+ }
+
+ const buf = Buffer.from(
+ `${eosConfig.eosRateUser}:${eosConfig.eosRatePassword}`,
+ 'utf8'
+ )
+ const auth = buf.toString('base64')
+
+ const { data } = await axiosUtil.instance.post(
+ eosConfig.eosRateUrl,
+ { ratesStatsInput: {} },
+ {
+ headers: { Authorization: `Basic ${auth}` }
+ }
+ )
+
+ return data?.getRatesStats?.bpsStats || []
+ },
+ options: {
+ auth: false
+ }
+}
diff --git a/hapi/src/routes/index.js b/hapi/src/routes/index.js
index 7659c589..ebb9797a 100644
--- a/hapi/src/routes/index.js
+++ b/hapi/src/routes/index.js
@@ -7,6 +7,7 @@ const transactionsRoute = require('./transactions.route')
const createFaucetAccountRoute = require('./create-faucet-account.route')
const transferFaucetTokensRoute = require('./transfer-faucet-tokens.route')
const getProducersInfoRoute = require('./get-producers-info.route')
+const getEOSRateStats = require('./get-eos-rate-stats.route')
module.exports = [
healthzRoute,
@@ -17,5 +18,6 @@ module.exports = [
transactionsRoute,
createFaucetAccountRoute,
transferFaucetTokensRoute,
- getProducersInfoRoute
+ getProducersInfoRoute,
+ getEOSRateStats
]
diff --git a/hasura/metadata/actions.graphql b/hasura/metadata/actions.graphql
index d0a790e8..4461801a 100644
--- a/hasura/metadata/actions.graphql
+++ b/hasura/metadata/actions.graphql
@@ -18,6 +18,10 @@ type Mutation {
): CreateAccountOutput
}
+type Query {
+ eosrate_stats: [EOSRateStats]
+}
+
type Mutation {
getProducersInfo(
bpParams: GetProducersInfoInput!
@@ -96,3 +100,14 @@ type GetProducersInfoOutput {
producersInfo: [jsonb]
}
+type EOSRateStats {
+ ratings_cntr: Int
+ transparency: Float
+ average: Float
+ infrastructure: Float
+ bp: String
+ development: Float
+ community: Float
+ trustiness: Float
+}
+
diff --git a/hasura/metadata/actions.yaml b/hasura/metadata/actions.yaml
index 1153922b..37b0a8a0 100644
--- a/hasura/metadata/actions.yaml
+++ b/hasura/metadata/actions.yaml
@@ -20,6 +20,10 @@ actions:
handler: '{{HASURA_GRAPHQL_ACTION_BASE_URL}}/create-faucet-account'
permissions:
- role: guest
+ - name: eosrate_stats
+ definition:
+ kind: ""
+ handler: '{{HASURA_GRAPHQL_ACTION_BASE_URL}}/get-eos-rate'
- name: getProducersInfo
definition:
kind: synchronous
@@ -65,4 +69,5 @@ custom_types:
- name: CreateAccountOutput
- name: TransferFaucetTokensOutput
- name: GetProducersInfoOutput
+ - name: EOSRateStats
scalars: []
diff --git a/webapp/src/components/InformationCard/EmptyState.js b/webapp/src/components/InformationCard/EmptyState.js
index a87198f5..d95b6c98 100644
--- a/webapp/src/components/InformationCard/EmptyState.js
+++ b/webapp/src/components/InformationCard/EmptyState.js
@@ -5,7 +5,7 @@ const EmptyState = ({ classes, t }) => {
return (
-
+
{
const [anchorEl, setAnchorEl] = useState(null)
@@ -49,12 +50,7 @@ const ProducerInformation = ({ info, classes, t }) => {
{t('website')}:
-
- window.open(info?.website, '_blank')}
- className={classes.clickableIcon}
- />
-
+
{
{t('email')}:
-
- (window.location = `mailto:${info.email}`)}
- className={classes.clickableIcon}
- />
-
-
+
{
{t('ownershipDisclosure')}:
-
- window.open(info?.ownership, '_blank')}
- className={classes.clickableIcon}
- />
-
+
{
{t('codeofconduct')}:
-
- window.open(info?.code_of_conduct, '_blank')}
- className={classes.clickableIcon}
- />
-
+
{
{t('chainResources')}:
-
- window.open(info?.chain, '_blank')}
- className={classes.clickableIcon}
- />
-
-
+
{
+const Stats = ({ missedBlocks, t, classes, votes, rewards, eosRate }) => {
if (eosConfig.networkName === 'lacchain') return <>>
return (
-
+
{t('stats')}
@@ -21,6 +22,20 @@ const Stats = ({ missedBlocks, t, classes, votes, rewards, type }) => {
}`}
+ {!!eosRate && (
+
+
+ {`${t('EOSRate')}:
+ ${eosRate.average.toFixed(2)} ${t('average')}
+ (${eosRate.ratings_cntr} ${t('ratings')})`}
+
+
+
+ )}
+
{!!generalConfig.historyEnabled && (
diff --git a/webapp/src/components/InformationCard/index.js b/webapp/src/components/InformationCard/index.js
index d5e277ca..35271aaf 100644
--- a/webapp/src/components/InformationCard/index.js
+++ b/webapp/src/components/InformationCard/index.js
@@ -87,6 +87,7 @@ const InformationCard = ({ producer, rank, type }) => {
producer.total_rewards || '0',
0,
)}
+ eosRate={producer?.eosRate}
/>
({
minWidth: 150,
},
},
+ ratings: {
+ [theme.breakpoints.up('lg')]: {
+ whiteSpace: 'pre-line !important',
+ },
+ },
boxLabel: {
alignItems: 'baseline !important',
},
@@ -223,6 +228,15 @@ export default (theme) => ({
minWidth: 130,
},
},
+ stats: {
+ '& .MuiTypography-body1': {
+ margin: theme.spacing(1, 0),
+ display: 'flex',
+ whiteSpace: 'nowrap',
+ textOverflow: 'ellipsis',
+ overflow: 'hidden',
+ },
+ },
social: {
borderLeft: 'none',
width: 100,
@@ -240,7 +254,7 @@ export default (theme) => ({
marginRight: theme.spacing(1),
},
[theme.breakpoints.up('lg')]: {
- minWidth: 150,
+ minWidth: 120,
},
},
dd: {
diff --git a/webapp/src/components/VisitSite/index.js b/webapp/src/components/VisitSite/index.js
new file mode 100644
index 00000000..0004149b
--- /dev/null
+++ b/webapp/src/components/VisitSite/index.js
@@ -0,0 +1,27 @@
+import React from 'react'
+import { makeStyles } from '@mui/styles'
+import Tooltip from '@mui/material/Tooltip'
+import LaunchIcon from '@mui/icons-material/Launch'
+
+import styles from './styles'
+
+const useStyles = makeStyles(styles)
+
+const VisitSite = ({ title, url, placement = 'left' }) => {
+ const classes = useStyles()
+
+ return (
+
+
+
+
+
+ )
+}
+
+export default VisitSite
diff --git a/webapp/src/components/VisitSite/styles.js b/webapp/src/components/VisitSite/styles.js
new file mode 100644
index 00000000..63177ea5
--- /dev/null
+++ b/webapp/src/components/VisitSite/styles.js
@@ -0,0 +1,14 @@
+export default (theme) => ({
+ link: {
+ width: '24px',
+ height: '24px',
+ marginLeft: theme.spacing(3),
+ },
+ clickableIcon: {
+ color: 'black',
+ cursor: 'pointer',
+ '&:hover': {
+ color: '#1565c0',
+ },
+ },
+})
diff --git a/webapp/src/gql/producer.gql.js b/webapp/src/gql/producer.gql.js
index 2b359d89..7d1808b9 100644
--- a/webapp/src/gql/producer.gql.js
+++ b/webapp/src/gql/producer.gql.js
@@ -215,3 +215,13 @@ export const ALL_NODES_QUERY = gql`
}
}
`
+
+export const EOSRATE_STATS_QUERY = gql`
+ query {
+ eosrate_stats {
+ bp
+ average
+ ratings_cntr
+ }
+ }
+`
diff --git a/webapp/src/hooks/customHooks/useBlockProducerState.js b/webapp/src/hooks/customHooks/useBlockProducerState.js
index 08f90c6c..0eeee421 100644
--- a/webapp/src/hooks/customHooks/useBlockProducerState.js
+++ b/webapp/src/hooks/customHooks/useBlockProducerState.js
@@ -1,7 +1,11 @@
import { useState, useEffect } from 'react'
-import { useSubscription } from '@apollo/client'
+import { useLazyQuery, useSubscription } from '@apollo/client'
-import { PRODUCERS_QUERY, BLOCK_TRANSACTIONS_HISTORY } from '../../gql'
+import {
+ PRODUCERS_QUERY,
+ BLOCK_TRANSACTIONS_HISTORY,
+ EOSRATE_STATS_QUERY,
+} from '../../gql'
import { eosConfig } from '../../config'
import useSearchState from './useSearchState'
@@ -23,15 +27,21 @@ const CHIPS_NAMES = ['all', ...eosConfig.producerTypes]
const useBlockProducerState = () => {
const [
- { filters, pagination, loading, producers },
+ { filters, pagination, producers },
{ handleOnSearch, handleOnPageChange, setPagination },
] = useSearchState({ query: PRODUCERS_QUERY })
const { data: dataHistory, loading: loadingHistory } = useSubscription(
BLOCK_TRANSACTIONS_HISTORY,
)
+ const [loadStats, { loading = true, data: { eosrate_stats: stats } = {} }] =
+ useLazyQuery(EOSRATE_STATS_QUERY)
const [items, setItems] = useState([])
const [missedBlocks, setMissedBlocks] = useState({})
+ useEffect(() => {
+ loadStats({})
+ }, [loadStats])
+
const chips = CHIPS_NAMES.map((e) => {
return { name: e }
})
@@ -46,7 +56,11 @@ const useBlockProducerState = () => {
...prev,
page: 1,
...filter,
- where: { ...where, owner: prev.where?.owner, bp_json: { _is_null: false } },
+ where: {
+ ...where,
+ owner: prev.where?.owner,
+ bp_json: { _is_null: false },
+ },
}))
}, [filters, setPagination])
@@ -54,11 +68,24 @@ const useBlockProducerState = () => {
let newItems = producers ?? []
if (eosConfig.networkName === 'lacchain' && filters.name !== 'all') {
- newItems = items.filter((producer) => producer.bp_json?.type === filters)
+ newItems = newItems.filter(
+ (producer) => producer.bp_json?.type === filters.name,
+ )
+ }
+
+ if (newItems?.length && stats?.length) {
+ newItems = newItems.map((producer) => {
+ return {
+ ...producer,
+ eosRate: Object.keys(producer.bp_json).length
+ ? stats.find((rate) => rate.bp === producer.owner)
+ : undefined,
+ }
+ })
}
setItems(newItems)
- }, [filters, producers, items])
+ }, [filters.name, stats, producers])
useEffect(() => {
if (dataHistory?.stats.length) {
diff --git a/webapp/src/language/en.json b/webapp/src/language/en.json
index 0d540a38..ff341d37 100644
--- a/webapp/src/language/en.json
+++ b/webapp/src/language/en.json
@@ -252,7 +252,9 @@
"peer_keys": "Peer Key",
"account_key": "Account Key",
"hs_bpJson": "BP Json",
- "emptyState": "This block producer does not provide any information."
+ "emptyState": "This block producer does not provide any information.",
+ "average": "average rating",
+ "ratings": "ratings"
},
"nodeCardComponent": {
"features": "Features",
diff --git a/webapp/src/language/es.json b/webapp/src/language/es.json
index f38ca56a..fe7b00f4 100644
--- a/webapp/src/language/es.json
+++ b/webapp/src/language/es.json
@@ -258,7 +258,9 @@
"peer_keys": "Peer",
"account_key": "Cuenta",
"hs_bpJson": "BP Json",
- "emptyState": "Este productor de bloques no proporciona ninguna información."
+ "emptyState": "Este productor de bloques no proporciona ninguna información.",
+ "average": "calificación promedio",
+ "ratings": "calificaciones"
},
"nodeCardComponent": {
"features": "Características",