diff --git a/backend/package.json b/backend/package.json
index 70690fe..63ed9a1 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -20,6 +20,7 @@
"connect-mongo": "^3.1.2",
"crypto-js": "^3.1.9-1",
"esm": "^3.2.20",
+ "ethers": "^5.6.1",
"express": "^4.17.1",
"express-session": "^1.17.0",
"graphql-request": "^4.0.0",
diff --git a/backend/src/config.js b/backend/src/config.js
index a2bec61..f1a7b04 100644
--- a/backend/src/config.js
+++ b/backend/src/config.js
@@ -1,4 +1,3 @@
-//Server configuration variables
export const {
NODE_PORT = 42609,
NODE_ENV = 'local',
@@ -17,7 +16,10 @@ export const {
CONF_TIMEOUT_CMC = 360000,
CONF_TIMEOUT_ALCHEMY = 2000,
CONF_TIMEOUT_LIVEPEER = 60000,
+ CONF_TIMEOUT_ENS_DOMAIN = 86400000,
+ CONF_TIMEOUT_ENS_INFO = 3600000,
CONF_DISABLE_SYNC = false,
CONF_DISABLE_DB = false,
- CONF_DISABLE_CMC = false
+ CONF_DISABLE_CMC = false,
+ CONF_DISABLE_ENS = false
} = process.env;
diff --git a/backend/src/routes/livepeer.js b/backend/src/routes/livepeer.js
index 0a329e5..5e5054d 100644
--- a/backend/src/routes/livepeer.js
+++ b/backend/src/routes/livepeer.js
@@ -8,9 +8,10 @@ import {
API_CMC, API_L1_HTTP, API_L2_HTTP, API_L2_WS,
CONF_DEFAULT_ORCH, CONF_SIMPLE_MODE, CONF_TIMEOUT_CMC,
CONF_TIMEOUT_ALCHEMY, CONF_TIMEOUT_LIVEPEER, CONF_DISABLE_SYNC,
- CONF_DISABLE_DB,
- CONF_DISABLE_CMC
+ CONF_DISABLE_DB, CONF_DISABLE_CMC, CONF_TIMEOUT_ENS_DOMAIN,
+ CONF_TIMEOUT_ENS_INFO, CONF_DISABLE_ENS
} from "../config";
+
// Do API requests to other API's
const https = require('https');
// Read ABI files
@@ -45,8 +46,14 @@ if (!CONF_SIMPLE_MODE) {
}
// For listening to blockchain events
+// ENS stuff TODO: CONF_DISABLE_ENS
+const { ethers } = require("ethers");
+const provider = new ethers.providers.JsonRpcProvider(API_L1_HTTP);
+// const ens = new ENS({ provider: web3layer1, ensAddress: getEnsAddress('1') });
+let ensDomainCache = [];
+let ensInfoCache = [];
+
// Update CoinMarketCap related api calls every 5 minutes
-const timeoutCMC = CONF_TIMEOUT_CMC;
let cmcPriceGet = 0;
let ethPrice = 0;
let lptPrice = 0;
@@ -54,7 +61,6 @@ let cmcQuotes = {};
let cmcCache = {};
// Update Alchemy related API calls every 2 seconds
-const timeoutAlchemy = CONF_TIMEOUT_ALCHEMY;
let l2Gwei = 0;
let l1Gwei = 0;
let l2block = 0;
@@ -82,8 +88,6 @@ let commissionFeeCostL2 = 0;
let serviceUriFeeCostL1 = 0;
let serviceUriFeeCostL2 = 0;
-// Update O info from thegraph every 1 minute
-const timeoutTheGraph = CONF_TIMEOUT_LIVEPEER;
// Will contain addr, lastGet, and obj of any requested O's
let orchestratorCache = [];
// Contains delegator addr and the address of the O they are bounded to
@@ -461,12 +465,12 @@ apiRouter.get("/grafana", async (req, res) => {
try {
const now = new Date().getTime();
// Update blockchain data if the cached data has expired
- if (now - arbGet > timeoutAlchemy) {
+ if (now - arbGet > CONF_TIMEOUT_ALCHEMY) {
await parseEthBlockchain();
arbGet = now;
}
// Update coin prices once their data has expired
- if (now - cmcPriceGet > timeoutCMC) {
+ if (now - cmcPriceGet > CONF_TIMEOUT_CMC) {
await parseCmc();
cmcPriceGet = now;
}
@@ -502,7 +506,7 @@ apiRouter.get("/cmc", async (req, res) => {
try {
const now = new Date().getTime();
// Update cmc once their data has expired
- if (now - cmcPriceGet > timeoutCMC) {
+ if (now - cmcPriceGet > CONF_TIMEOUT_CMC) {
cmcPriceGet = now;
await parseCmc();
}
@@ -517,7 +521,7 @@ apiRouter.get("/blockchains", async (req, res) => {
try {
const now = new Date().getTime();
// Update blockchain data if the cached data has expired
- if (now - arbGet > timeoutAlchemy) {
+ if (now - arbGet > CONF_TIMEOUT_ALCHEMY) {
arbGet = now;
await parseEthBlockchain();
}
@@ -551,7 +555,7 @@ apiRouter.get("/quotes", async (req, res) => {
try {
const now = new Date().getTime();
// Update cmc once their data has expired
- if (now - cmcPriceGet > timeoutCMC) {
+ if (now - cmcPriceGet > CONF_TIMEOUT_CMC) {
cmcPriceGet = now;
await parseCmc();
}
@@ -596,17 +600,18 @@ const parseOrchestrator = async function (reqAddr) {
}
}
if (wasCached) {
- if (now - orchestratorObj.lastGet < timeoutTheGraph) {
+ if (now - orchestratorObj.lastGet < CONF_TIMEOUT_LIVEPEER) {
needsUpdate = false;
}
}
if (!wasCached || needsUpdate) {
const orchQuery = gql`{
- transcoders(where: {id: "${reqAddr}"}) {
+ transcoder(id: "${reqAddr}") {
id
activationRound
deactivationRound
active
+ status
lastRewardRound {
id
length
@@ -629,7 +634,7 @@ const parseOrchestrator = async function (reqAddr) {
totalVolumeETH
totalVolumeUSD
serviceURI
- delegators {
+ delegators(first: 1000) {
id
bondedAmount
startRound
@@ -643,20 +648,22 @@ const parseOrchestrator = async function (reqAddr) {
}
`;
orchestratorObj = await request("https://api.thegraph.com/subgraphs/name/livepeer/arbitrum-one", orchQuery);
- orchestratorObj = orchestratorObj.transcoders[0];
+ orchestratorObj = orchestratorObj.transcoder;
// Not found
if (!orchestratorObj) {
return {};
}
orchestratorObj.lastGet = now;
if (wasCached) {
- for (var orch of orchestratorCache) {
- if (orch.id == reqAddr) {
- orch = orchestratorObj;
+ for (var idx = 0; idx < orchestratorCache.length; idx++) {
+ if (orchestratorCache[idx].id == reqAddr) {
+ console.log("Updating outdated orchestrator " + orchestratorObj.id + " @ " + now);
+ orchestratorCache[idx] = orchestratorObj;
break;
}
}
} else {
+ console.log("Pushing new orchestrator " + orchestratorObj.id + " @ " + now);
orchestratorCache.push(orchestratorObj);
}
}
@@ -714,7 +721,7 @@ const parseDelegator = async function (reqAddr) {
}
}
if (wasCached) {
- if (now - delegatorObj.lastGet < timeoutTheGraph) {
+ if (now - delegatorObj.lastGet < CONF_TIMEOUT_LIVEPEER) {
needsUpdate = false;
}
}
@@ -738,13 +745,15 @@ const parseDelegator = async function (reqAddr) {
}
delegatorObj.lastGet = now;
if (wasCached) {
- for (var delegator of delegatorCache) {
- if (delegator.id == reqAddr) {
- delegator = delegatorObj;
+ for (var idx = 0; idx < delegatorCache.length; idx++) {
+ if (delegatorCache[idx].id == reqAddr) {
+ console.log("Updating outdated delegator " + delegatorObj.id + " @ " + now);
+ delegatorCache[idx] = delegatorObj;
break;
}
}
} else {
+ console.log("Pushing new delegator " + delegatorObj.id + " @ " + now);
delegatorCache.push(delegatorObj);
}
}
@@ -804,12 +813,12 @@ apiRouter.get("/prometheus/:orchAddr", async (req, res) => {
try {
const now = new Date().getTime();
// Update blockchain data if the cached data has expired
- if (now - arbGet > timeoutAlchemy) {
+ if (now - arbGet > CONF_TIMEOUT_ALCHEMY) {
await parseEthBlockchain();
arbGet = now;
}
// Update coin prices once their data has expired
- if (now - cmcPriceGet > timeoutCMC) {
+ if (now - cmcPriceGet > CONF_TIMEOUT_CMC) {
await parseCmc();
cmcPriceGet = now;
}
@@ -950,4 +959,121 @@ apiRouter.get("/prometheus/:orchAddr", async (req, res) => {
}
});
+const getEnsDomain = async function (addr) {
+ const now = new Date().getTime();
+ let wasInCache = false;
+ // See if it is cached
+ for (const thisAddr of ensDomainCache) {
+ if (thisAddr.address === addr) {
+ // Check timeout
+ if (now - thisAddr.timestamp < CONF_TIMEOUT_ENS_DOMAIN ){
+ return thisAddr.domain;
+ }
+ wasInCache = true;
+ }
+ }
+ // Else get it and cache it
+ const ensDomain = await provider.lookupAddress(addr.toLowerCase());
+ let ensObj;
+ if (!ensDomain){
+ ensObj = {
+ domain: null,
+ address: addr,
+ timestamp: now
+ };
+ } else {
+ ensObj = {
+ domain: ensDomain,
+ address: addr,
+ timestamp: now
+ };
+ }
+ if (wasInCache){
+ for (var idx = 0; idx < ensDomainCache.length; idx++) {
+ if (ensDomainCache[idx].address == addr) {
+ console.log("Updating outdated domain " + ensObj.domain + " owned by " + ensObj.address + " @ " + ensObj.timestamp);
+ ensDomainCache[idx] = ensObj;
+ break;
+ }
+ }
+ } else {
+ console.log("Caching new domain " + ensObj.domain + " owned by " + ensObj.address + " @ " + ensObj.timestamp);
+ ensDomainCache.push(ensObj);
+ }
+ return ensObj.domain;
+}
+
+const getEnsInfo = async function (addr) {
+ const now = new Date().getTime();
+ let wasInCache = false;
+ // See if it is cached
+ for (const thisAddr of ensInfoCache) {
+ if (thisAddr.domain === addr) {
+ // Check timeout
+ if (now - thisAddr.timestamp < CONF_TIMEOUT_ENS_INFO ){
+ return thisAddr;
+ }
+ wasInCache = true;
+ }
+ }
+ // Else get it and cache it
+ const resolver = await provider.getResolver(addr);
+ const description = await resolver.getText("description");
+ const url = await resolver.getText("url");
+ const avatar = await resolver.getAvatar();
+ const ensObj = {
+ domain: addr,
+ description,
+ url,
+ avatar,
+ timestamp: now
+ };
+ if (wasInCache){
+ for (var idx = 0; idx < ensInfoCache.length; idx++) {
+ if (ensInfoCache[idx].domain == addr) {
+ console.log("Updating outdated info " + ensObj.domain + " @ " + ensObj.timestamp);
+ ensInfoCache[idx] = ensObj;
+ break;
+ }
+ }
+ } else {
+ console.log("Caching new info " + ensObj.domain + " @ " + ensObj.timestamp);
+ ensInfoCache.push(ensObj);
+ }
+ return ensObj;
+}
+
+// Gets and caches info for a single address
+apiRouter.get("/getENS/:orch", async (req, res) => {
+ try {
+ // First resolve addr => domain name
+ const ensDomain = await getEnsDomain(req.params.orch);
+ if (!ensDomain){
+ res.send({domain: null});
+ return;
+ }
+ // Then resolve address to info
+ const ensInfo = await getEnsInfo(ensDomain);
+ res.send(ensInfo);
+ } catch (err) {
+ res.status(400).send(err);
+ }
+});
+// Returns entire ENS domain mapping cache
+apiRouter.get("/getEnsDomains", async (req, res) => {
+ try {
+ res.send(ensDomainCache);
+ } catch (err) {
+ res.status(400).send(err);
+ }
+});
+// Returns entire ENS info mapping cache
+apiRouter.get("/getEnsInfo", async (req, res) => {
+ try {
+ res.send(ensInfoCache);
+ } catch (err) {
+ res.status(400).send(err);
+ }
+});
+
export default apiRouter;
\ No newline at end of file
diff --git a/public/ens.png b/public/ens.png
new file mode 100644
index 0000000..585dfe6
Binary files /dev/null and b/public/ens.png differ
diff --git a/src/actions/livepeer.js b/src/actions/livepeer.js
index e448fc2..ba3f162 100644
--- a/src/actions/livepeer.js
+++ b/src/actions/livepeer.js
@@ -24,6 +24,8 @@ export const RECEIVE_CURRENT_ORCHESTRATOR = "RECEIVE_CURRENT_ORCHESTRATOR";
export const RECEIVE_ORCHESTRATOR = "RECEIVE_ORCHESTRATOR";
export const CLEAR_ORCHESTRATOR = "CLEAR_ORCHESTRATOR";
export const RECEIVE_TICKETS = "RECEIVE_TICKETS";
+export const SET_ALL_ENS_INFO = "SET_ALL_ENS_INFO";
+export const SET_ALL_ENS_DOMAINS = "SET_ALL_ENS_DOMAINS";
const setQuotes = message => ({
type: RECEIVE_QUOTES, message
@@ -46,6 +48,13 @@ const clearOrchestratorInfo = () => ({
const setTickets = message => ({
type: RECEIVE_TICKETS, message
});
+const setAllEnsInfo = message => ({
+ type: SET_ALL_ENS_INFO, message
+});
+const setAllEnsDomains = message => ({
+ type: SET_ALL_ENS_DOMAINS, message
+});
+
export const getQuotes = () => async dispatch => {
const response = await apiUtil.getQuotes();
@@ -522,4 +531,28 @@ export const getOrchestratorInfo = (orchAddr) => async dispatch => {
export const clearOrchestrator = () => async dispatch => {
return dispatch(clearOrchestratorInfo({}));
-};
\ No newline at end of file
+};
+
+export const getAllEnsDomains = () => async dispatch => {
+ const response = await apiUtil.getAllEnsDomains();
+ const data = await response.json();
+ if (response.ok) {
+ return dispatch(setAllEnsDomains(data));
+ }
+ return dispatch(receiveErrors(data));
+};
+
+export const getAllEnsInfo = () => async dispatch => {
+ const response = await apiUtil.getAllEnsInfo();
+ const data = await response.json();
+ if (response.ok) {
+ return dispatch(setAllEnsInfo(data));
+ }
+ return dispatch(receiveErrors(data));
+};
+
+export const getEnsInfo = async (addr) => {
+ const response = await apiUtil.getEnsInfo(addr);
+ const data = await response.json();
+};
+
\ No newline at end of file
diff --git a/src/components/OrchAddressViewer.js b/src/components/OrchAddressViewer.js
index b9365c3..c20fbeb 100644
--- a/src/components/OrchAddressViewer.js
+++ b/src/components/OrchAddressViewer.js
@@ -1,14 +1,94 @@
-import React from "react";
+import React, { useState } from "react";
+import { useSelector } from 'react-redux';
+import { getEnsInfo } from "../actions/livepeer";
+import {
+ getOrchestratorInfo
+} from "../actions/livepeer";
const Address = (obj) => {
+ const livepeer = useSelector((state) => state.livepeerstate);
+ const [hasRefreshed, setRefresh] = useState(false);
+ let thisDomain = null;
+ let thisInfo = null;
+ const now = new Date().getTime();
+ // Lookup domain in cache
+ if (livepeer.ensDomainMapping){
+ for (const thisAddr of livepeer.ensDomainMapping) {
+ if (thisAddr.address === obj.address) {
+ thisDomain = thisAddr;
+ // Check timeout
+ if (now - thisAddr.timestamp < 86400000) {
+ break;
+ }
+ // Is outdated
+ if (!hasRefreshed) {
+ getEnsInfo(obj.address);
+ setRefresh(true);
+ }
+ }
+ }
+ // If it was not cached at all
+ if (thisDomain == null && !hasRefreshed) {
+ setRefresh(true);
+ getEnsInfo(obj.address);
+ }
+ }
+ // Lookup current info in cache only if this addr has a mapped ENS domain
+ if (thisDomain && thisDomain.domain) {
+ for (const thisAddr of livepeer.ensInfoMapping) {
+ if (thisAddr.domain === thisDomain.domain) {
+ thisInfo = thisAddr;
+ // Check timeout
+ if (now - thisAddr.timestamp < 86400000) {
+ break;
+ }
+ // Is outdated
+ if (!hasRefreshed) {
+ getEnsInfo(obj.address);
+ setRefresh(true);
+ }
+ }
+ }
+ // If it was not cached at all
+ if (thisInfo == null && !hasRefreshed) {
+ getEnsInfo(obj.address);
+ setRefresh(true);
+ }
+ }
+
+ let thisName;
+ let thisIcon;
+ if (thisInfo) {
+ thisName = thisInfo.domain;
+ if (thisInfo.avatar) {
+ thisIcon =
+
+
+
+