diff --git a/feeds-ui/src/components/networkGraph/NetworkGraph.component.js b/feeds-ui/src/components/networkGraph/NetworkGraph.component.js
index 9e8e21e85a5..66cc325f0b2 100644
--- a/feeds-ui/src/components/networkGraph/NetworkGraph.component.js
+++ b/feeds-ui/src/components/networkGraph/NetworkGraph.component.js
@@ -81,6 +81,7 @@ const NetworkGraph = ({
diff --git a/feeds-ui/src/components/networkGraph/NetworkGraph.d3.js b/feeds-ui/src/components/networkGraph/NetworkGraph.d3.js
index c04398fc2a7..907c7e4b628 100644
--- a/feeds-ui/src/components/networkGraph/NetworkGraph.d3.js
+++ b/feeds-ui/src/components/networkGraph/NetworkGraph.d3.js
@@ -40,7 +40,11 @@ export default class NetworkGraph {
.style('opacity', 0)
}
- updateNodes(nodes) {
+ updateNodes(updatedNodes) {
+ // Prevent from update redux store
+ // Fix it after D3 refactor
+ const nodes = JSON.parse(JSON.stringify(updatedNodes))
+
const updateData = this.svg
.select('.network-graph__nodes')
.selectAll('g.network-graph__node-group')
@@ -189,6 +193,7 @@ export default class NetworkGraph {
this.oracleTooltip.select('.price').text('')
this.oracleTooltip.select('.date').text('')
this.oracleTooltip.select('.block').text('')
+ this.oracleTooltip.select('.gas').text('')
this.oracleTooltip.select('.name').text(() => d.name)
@@ -211,6 +216,10 @@ export default class NetworkGraph {
this.oracleTooltip
.select('.block')
.text(() => `Block: ${d.state.meta.blockNumber}`)
+
+ this.oracleTooltip
+ .select('.gas')
+ .text(() => `Gas Price (Gwei): ${d.state.meta.gasPrice}`)
}
}
diff --git a/feeds-ui/src/components/oracleTable/OracleTable.component.js b/feeds-ui/src/components/oracleTable/OracleTable.component.js
new file mode 100644
index 00000000000..abe580c7bb2
--- /dev/null
+++ b/feeds-ui/src/components/oracleTable/OracleTable.component.js
@@ -0,0 +1,95 @@
+import React, { useEffect, useState } from 'react'
+import { humanizeUnixTimestamp } from 'utils'
+import _ from 'lodash'
+import { Table, Icon } from 'antd'
+
+const OracleTable = ({
+ networkGraphState,
+ networkGraphNodes,
+ fetchEthGasPrice,
+ ethGasPrice,
+}) => {
+ const [data, setData] = useState()
+ const [gasPrice, setGasPrice] = useState()
+
+ useEffect(() => {
+ fetchEthGasPrice()
+ }, [fetchEthGasPrice])
+
+ useEffect(() => {
+ if (ethGasPrice) {
+ setGasPrice(ethGasPrice.fast / 10)
+ }
+ }, [ethGasPrice])
+
+ useEffect(() => {
+ const mergedData = networkGraphNodes
+ .filter(node => node.type === 'oracle')
+ .map(oracle => {
+ const state = _.find(networkGraphState, { sender: oracle.address })
+ return {
+ oracle: oracle,
+ state,
+ key: oracle.id,
+ }
+ })
+ setData(mergedData)
+ }, [networkGraphState, networkGraphNodes])
+
+ const columns = [
+ {
+ title: 'Oracle',
+ dataIndex: 'oracle.name',
+ key: 'name',
+ sorter: (a, b) =>
+ a.oracle.name.localeCompare(b && b.oracle && b.oracle.name),
+ },
+ {
+ title: 'Answer',
+ dataIndex: 'state.responseFormatted',
+ key: 'answer',
+ sorter: (a, b) => {
+ if (!a.state || !b.state) return
+
+ return a.state.responseFormatted - b.state.responseFormatted
+ },
+ },
+ {
+ title: 'Gas Price (Gwei)',
+ dataIndex: 'state.meta.gasPrice',
+ key: 'gas',
+ sorter: (a, b) => {
+ if (!a.state || !b.state) return
+ return a.state.meta.gasPrice - b.state.meta.gasPrice
+ },
+ defaultSortOrder: 'descend',
+ },
+ {
+ title: 'Date',
+ dataIndex: 'state.meta.timestamp',
+ key: 'timestamp',
+ render: timestamp => humanizeUnixTimestamp(timestamp),
+ },
+ ]
+
+ return (
+
+
Oracles data
+
+
+ Recommended gas price:{' '}
+ {gasPrice} Gwei
+
+
+
}}
+ />
+
+ )
+}
+
+export default OracleTable
diff --git a/feeds-ui/src/components/oracleTable/OracleTable.enhanced.js b/feeds-ui/src/components/oracleTable/OracleTable.enhanced.js
new file mode 100644
index 00000000000..141d2eaabca
--- /dev/null
+++ b/feeds-ui/src/components/oracleTable/OracleTable.enhanced.js
@@ -0,0 +1,19 @@
+import OracleTable from './OracleTable.component'
+import { connect } from 'react-redux'
+
+import {
+ aggregationSelectors,
+ aggregationOperations,
+} from 'state/ducks/aggregation'
+
+const mapStateToProps = state => ({
+ networkGraphNodes: aggregationSelectors.networkGraphNodes(state),
+ networkGraphState: aggregationSelectors.networkGraphState(state),
+ ethGasPrice: state.aggregation.ethGasPrice,
+})
+
+const mapDispatchToProps = {
+ fetchEthGasPrice: aggregationOperations.fetchEthGasPrice,
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(OracleTable)
diff --git a/feeds-ui/src/components/oracleTable/index.js b/feeds-ui/src/components/oracleTable/index.js
new file mode 100644
index 00000000000..3326c2baa30
--- /dev/null
+++ b/feeds-ui/src/components/oracleTable/index.js
@@ -0,0 +1 @@
+export { default as OracleTable } from './OracleTable.enhanced'
diff --git a/feeds-ui/src/contracts/AggregationContract.js b/feeds-ui/src/contracts/AggregationContract.js
index 58e6d48a485..bb26f1c08c6 100644
--- a/feeds-ui/src/contracts/AggregationContract.js
+++ b/feeds-ui/src/contracts/AggregationContract.js
@@ -116,6 +116,19 @@ export default class AggregationContract {
})
}
+ async addGasPriceToLogs(logs) {
+ if (!logs) return logs
+
+ const logsWithGasPriceOps = logs.map(async log => {
+ const tx = await this.provider.getTransaction(log.meta.transactionHash)
+ // eslint-disable-next-line require-atomic-updates
+ log.meta.gasPrice = ethers.utils.formatUnits(tx.gasPrice, 'gwei')
+ return log
+ })
+
+ return Promise.all(logsWithGasPriceOps)
+ }
+
async oracleResponseLogs({ answerId, fromBlock }) {
const answerIdHex = ethers.utils.hexlify(answerId)
@@ -198,7 +211,11 @@ export default class AggregationContract {
}),
)
const logWithTimestamp = await this.addBlockTimestampToLogs([logged])
- return callback ? callback(logWithTimestamp[0]) : logWithTimestamp[0]
+ const logWithGasPrice = await this.addGasPriceToLogs(
+ logWithTimestamp,
+ ).then(l => l[0])
+
+ return callback ? callback(logWithGasPrice) : logWithGasPrice
}),
)
}
diff --git a/feeds-ui/src/pages/EthUsdPage.js b/feeds-ui/src/pages/EthUsdPage.js
index fa732eb3222..bf52c463d02 100644
--- a/feeds-ui/src/pages/EthUsdPage.js
+++ b/feeds-ui/src/pages/EthUsdPage.js
@@ -8,6 +8,7 @@ import { NetworkGraph } from 'components/networkGraph'
import { NetworkGraphInfo } from 'components/networkGraphInfo'
import { AnswerHistory } from 'components/answerHistory'
import { DeviationHistory } from 'components/deviationHistory'
+import { OracleTable } from 'components/oracleTable'
const OPTIONS = {
contractAddress: '0x79fEbF6B9F76853EDBcBc913e6aAE8232cFB9De9',
@@ -42,6 +43,7 @@ const NetworkPage = ({ initContract, clearState }) => {
+
)
}
diff --git a/feeds-ui/src/pages/Ropsten.js b/feeds-ui/src/pages/Ropsten.js
index 8b07261ef0d..e4f75757e66 100644
--- a/feeds-ui/src/pages/Ropsten.js
+++ b/feeds-ui/src/pages/Ropsten.js
@@ -6,6 +6,7 @@ import { NetworkGraphInfo } from 'components/networkGraphInfo'
import { AnswerHistory } from 'components/answerHistory'
import { DeviationHistory } from 'components/deviationHistory'
import withRopsten from 'enhancers/withRopsten'
+import { OracleTable } from 'components/oracleTable'
const NetworkPage = ({ initContract, clearState, options }) => {
useEffect(() => {
@@ -25,6 +26,7 @@ const NetworkPage = ({ initContract, clearState, options }) => {
+
)
}
diff --git a/feeds-ui/src/state/ducks/aggregation/actions.js b/feeds-ui/src/state/ducks/aggregation/actions.js
index 5ec91118c67..30922358624 100755
--- a/feeds-ui/src/state/ducks/aggregation/actions.js
+++ b/feeds-ui/src/state/ducks/aggregation/actions.js
@@ -63,3 +63,8 @@ export const setOptions = payload => ({
export const clearState = () => ({
type: types.CLEAR_STATE,
})
+
+export const setEthGasPrice = payload => ({
+ type: types.ETHGAS_PRICE,
+ payload,
+})
diff --git a/feeds-ui/src/state/ducks/aggregation/operations.js b/feeds-ui/src/state/ducks/aggregation/operations.js
index e2bef80030f..1f13b14b14e 100755
--- a/feeds-ui/src/state/ducks/aggregation/operations.js
+++ b/feeds-ui/src/state/ducks/aggregation/operations.js
@@ -65,10 +65,16 @@ const fetchOracleResponseById = request => {
const logs = await contractInstance.oracleResponseLogs(request)
const withTimestamp = await contractInstance.addBlockTimestampToLogs(logs)
+ const withGasAndTimeStamp = await contractInstance.addGasPriceToLogs(
+ withTimestamp,
+ )
- const uniquePayload = _.uniqBy([...withTimestamp, ...currentLogs], l => {
- return l.sender
- })
+ const uniquePayload = _.uniqBy(
+ [...withGasAndTimeStamp, ...currentLogs],
+ l => {
+ return l.sender
+ },
+ )
dispatch(actions.setOracleResponse(uniquePayload))
} catch {
@@ -312,4 +318,16 @@ const fetchJobId = address => {
}
}
-export { initContract, clearState, fetchJobId }
+const fetchEthGasPrice = () => {
+ return async dispatch => {
+ try {
+ const data = await fetch('https://ethgasstation.info/json/ethgasAPI.json')
+ const jsonData = await data.json()
+ dispatch(actions.setEthGasPrice(jsonData))
+ } catch {
+ console.error('Could not fetch gas price')
+ }
+ }
+}
+
+export { initContract, clearState, fetchJobId, fetchEthGasPrice }
diff --git a/feeds-ui/src/state/ducks/aggregation/reducers.js b/feeds-ui/src/state/ducks/aggregation/reducers.js
index e8bda73aa4e..b2882b93bd7 100755
--- a/feeds-ui/src/state/ducks/aggregation/reducers.js
+++ b/feeds-ui/src/state/ducks/aggregation/reducers.js
@@ -13,6 +13,7 @@ export const initialState = {
minimumResponses: null,
updateHeight: null,
answerHistory: null,
+ ethGasPrice: null,
}
function clearState(state) {
@@ -101,6 +102,12 @@ const reducer = (state = initialState, action) => {
answerHistory: action.payload,
}
+ case types.ETHGAS_PRICE:
+ return {
+ ...state,
+ ethGasPrice: action.payload,
+ }
+
default:
return state
}
diff --git a/feeds-ui/src/state/ducks/aggregation/types.js b/feeds-ui/src/state/ducks/aggregation/types.js
index 84d5755e2de..285f45b825b 100755
--- a/feeds-ui/src/state/ducks/aggregation/types.js
+++ b/feeds-ui/src/state/ducks/aggregation/types.js
@@ -12,3 +12,4 @@ export const REQUEST_TIME = 'aggregation/REQUEST_TIME'
export const MINUMUM_RESPONSES = 'aggregation/MINUMUM_RESPONSES'
export const UPDATE_HEIGHT = 'aggregation/UPDATE_HEIGHT'
export const ANSWER_HISTORY = 'aggregation/ANSWER_HISTORY'
+export const ETHGAS_PRICE = 'aggregation/ETHGAS_PRICE'
diff --git a/feeds-ui/src/theme.css b/feeds-ui/src/theme.css
index d1ae42c4c2d..64926578e24 100644
--- a/feeds-ui/src/theme.css
+++ b/feeds-ui/src/theme.css
@@ -17338,7 +17338,7 @@ span.ant-radio + * {
color: rgba(0, 0, 0, 0.85);
font-weight: 500;
text-align: left;
- background: #fafafa;
+ background: #f1f1f1;
border-bottom: 1px solid #e8e8e8;
transition: background 0.3s ease;
}
@@ -17424,12 +17424,12 @@ span.ant-radio + * {
.ant-table-thead > tr > th.ant-table-column-has-actions.ant-table-column-has-filters .anticon-filter.ant-table-filter-open,
.ant-table-thead > tr > th.ant-table-column-has-actions.ant-table-column-has-filters .ant-table-filter-icon.ant-table-filter-open {
color: rgba(0, 0, 0, 0.45);
- background: #e5e5e5;
+ background: #dddddd;
}
.ant-table-thead > tr > th.ant-table-column-has-actions.ant-table-column-has-filters:hover .anticon-filter:hover,
.ant-table-thead > tr > th.ant-table-column-has-actions.ant-table-column-has-filters:hover .ant-table-filter-icon:hover {
color: rgba(0, 0, 0, 0.45);
- background: #e5e5e5;
+ background: #dddddd;
}
.ant-table-thead > tr > th.ant-table-column-has-actions.ant-table-column-has-filters:hover .anticon-filter:active,
.ant-table-thead > tr > th.ant-table-column-has-actions.ant-table-column-has-filters:hover .ant-table-filter-icon:active {
@@ -17439,11 +17439,11 @@ span.ant-radio + * {
cursor: pointer;
}
.ant-table-thead > tr > th.ant-table-column-has-actions.ant-table-column-has-sorters:hover {
- background: #f2f2f2;
+ background: #e9e9e9;
}
.ant-table-thead > tr > th.ant-table-column-has-actions.ant-table-column-has-sorters:hover .anticon-filter,
.ant-table-thead > tr > th.ant-table-column-has-actions.ant-table-column-has-sorters:hover .ant-table-filter-icon {
- background: #f2f2f2;
+ background: #e9e9e9;
}
.ant-table-thead > tr > th.ant-table-column-has-actions.ant-table-column-has-sorters:active .ant-table-column-sorter-up:not(.on),
.ant-table-thead > tr > th.ant-table-column-has-actions.ant-table-column-has-sorters:active .ant-table-column-sorter-down:not(.on) {
@@ -17500,11 +17500,11 @@ span.ant-radio + * {
.ant-table-tbody > tr.ant-table-row-hover:not(.ant-table-expanded-row):not(.ant-table-row-selected) > td,
.ant-table-thead > tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected) > td,
.ant-table-tbody > tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected) > td {
- background: #f0f6ff;
+ background: #e6e6e6;
}
.ant-table-thead > tr.ant-table-row-selected > td.ant-table-column-sort,
.ant-table-tbody > tr.ant-table-row-selected > td.ant-table-column-sort {
- background: #fafafa;
+ background: #e6e6e6;
}
.ant-table-thead > tr:hover.ant-table-row-selected > td,
.ant-table-tbody > tr:hover.ant-table-row-selected > td {
@@ -17512,7 +17512,7 @@ span.ant-radio + * {
}
.ant-table-thead > tr:hover.ant-table-row-selected > td.ant-table-column-sort,
.ant-table-tbody > tr:hover.ant-table-row-selected > td.ant-table-column-sort {
- background: #fafafa;
+ background: #e6e6e6;
}
.ant-table-thead > tr:hover {
background: none;
@@ -17570,7 +17570,7 @@ span.ant-radio + * {
background: #fafafa;
}
.ant-table-thead > tr > th.ant-table-column-sort {
- background: #f5f5f5;
+ background: #f1f1f1;
}
.ant-table-tbody > tr > td.ant-table-column-sort {
background: rgba(0, 0, 0, 0.01);
@@ -17587,7 +17587,7 @@ span.ant-radio + * {
}
.ant-table-header {
overflow: hidden;
- background: #fafafa;
+ background: #f1f1f1;
}
.ant-table-header table {
border-radius: 4px 4px 0 0;
@@ -19531,7 +19531,7 @@ textarea.ant-time-picker-input {
border-radius: 0;
}
.ant-transfer-customize-list .ant-table-wrapper .ant-table-small > .ant-table-content > .ant-table-body > table > .ant-table-thead > tr > th {
- background: #fafafa;
+ background: #f1f1f1;
}
.ant-transfer-customize-list .ant-table-wrapper .ant-table-small > .ant-table-content .ant-table-row:last-child td {
border-bottom: 1px solid #e8e8e8;
@@ -21651,3 +21651,22 @@ hr {
font-size: 12px;
font-weight: 200;
}
+.oracle-table {
+ margin: 120px 0;
+}
+.oracle-table-header {
+ font-weight: 200;
+ position: relative;
+}
+.oracle-table .ant-table-placeholder {
+ background: transparent;
+}
+.oracle-table .gas-price-info {
+ padding: 20px;
+ margin: 20px 0;
+ border: 1px solid #d0d0d0;
+ font-size: 16px;
+}
+.oracle-table .gas-price-info h4 {
+ margin: 0;
+}
diff --git a/feeds-ui/src/theme/network-graph.less b/feeds-ui/src/theme/network-graph.less
index 9ca0f0cd577..868cfb28381 100644
--- a/feeds-ui/src/theme/network-graph.less
+++ b/feeds-ui/src/theme/network-graph.less
@@ -345,3 +345,26 @@
font-weight: 200;
}
}
+
+.oracle-table {
+ margin: 120px 0;
+
+ &-header {
+ font-weight: 200;
+ position: relative;
+ }
+
+ .ant-table-placeholder {
+ background: transparent;
+ }
+
+ .gas-price-info {
+ padding: 20px;
+ margin: 20px 0;
+ border: 1px solid #d0d0d0;
+ font-size: 16px;
+ h4 {
+ margin: 0;
+ }
+ }
+}
diff --git a/feeds-ui/src/theme/theme.less b/feeds-ui/src/theme/theme.less
index 878bac7ae0c..bb7ecb66c6b 100644
--- a/feeds-ui/src/theme/theme.less
+++ b/feeds-ui/src/theme/theme.less
@@ -5,3 +5,7 @@
@primary-color: #2a59da;
@link-color: #2a59da;
+@table-row-hover-bg: #e6e6e6;
+@table-header-bg: #f1f1f1;
+@table-body-selected-sort-bg: #e6e6e6;
+@table-header-sort-bg: #f1f1f1;