diff --git a/backend/ecosystem.config.js b/backend/ecosystem.config.js index 73466da..6a74f0a 100644 --- a/backend/ecosystem.config.js +++ b/backend/ecosystem.config.js @@ -2,7 +2,7 @@ module.exports = { apps : [{ name : "backend", script : "./src/index.js", - cwd : "/var/www/backend", + cwd : "/var/www/nframe/backend", env_production: { NODE_ENV: "production" }, diff --git a/backend/package.json b/backend/package.json index 74eb044..3052426 100644 --- a/backend/package.json +++ b/backend/package.json @@ -14,22 +14,24 @@ "author": "Marco van Dijk", "license": "WTFPL", "dependencies": { - "@alch/alchemy-web3": "^1.2.4", + "@alch/alchemy-web3": "^1.4.7", "alchemy-api": "^1.3.3", + "alchemy-sdk": "^2.2.4", "coinmarketcap-api": "^3.1.1", - "connect-mongo": "^3.1.2", - "crypto-js": "^3.1.9-1", - "esm": "^3.2.20", - "ethers": "^5.6.1", + "connect-mongo": "^4.6.0", + "connect-mongodb-session": "^3.1.1", + "crypto-js": "^4.1.1", + "esm": "^3.2.25", + "ethers": "^5.7.2", "express": "^4.17.1", "express-session": "^1.17.0", "graphql-request": "^4.0.0", "install": "^0.13.0", "joi": "^14.3.1", - "mongoose": "^5.12.3", + "mongoose": "^6.8.0", "npm": "^8.5.2", - "prom-client": "^14.0.1", - "web3": "^1.7.0" + "prom-client": "^14.1.0", + "web3": "^1.8.1" }, "devDependencies": { "nodemon": "^1.18.10" diff --git a/backend/src/config.js b/backend/src/config.js index f1a7b04..cf35e9f 100644 --- a/backend/src/config.js +++ b/backend/src/config.js @@ -8,9 +8,10 @@ export const { SESS_SECRET = 'secret!session', SESS_LIFETIME = (1000 * 60 * 60) * 24 * 365, API_CMC = "Coinmarketcap API key", - API_L1_HTTP = "ETH L1 HTTP API KEY", - API_L2_HTTP = "ETH L2 HTTP API KEY", - API_L2_WS = "ETH L2 WSS API KEY", + API_L1_HTTP = "ETH L1 HTTP API URL", + API_L2_HTTP = "ETH L2 HTTP API URL", + API_L1_KEY = "ETH L1 HTTP API KEY", + API_L2_KEY = "ETH L@ HTTP API KEY", CONF_DEFAULT_ORCH = "YOUR OWN ORCHESTRATORS PUBLIC ADDRESS", CONF_SIMPLE_MODE = false, CONF_TIMEOUT_CMC = 360000, diff --git a/backend/src/routes/livepeer.js b/backend/src/routes/livepeer.js index da2d6a4..e4e8a2c 100644 --- a/backend/src/routes/livepeer.js +++ b/backend/src/routes/livepeer.js @@ -19,7 +19,7 @@ import TotalStakeDataPoint from "../models/TotalStakeDataPoint"; const apiRouter = express.Router(); import { - API_CMC, API_L1_HTTP, API_L2_HTTP, + API_CMC, API_L1_HTTP, API_L1_KEY, API_L2_KEY, CONF_DEFAULT_ORCH, CONF_SIMPLE_MODE, CONF_TIMEOUT_CMC, CONF_TIMEOUT_ALCHEMY, CONF_TIMEOUT_LIVEPEER, CONF_DISABLE_DB, CONF_DISABLE_CMC, CONF_TIMEOUT_ENS_DOMAIN, @@ -60,14 +60,15 @@ if (!CONF_DISABLE_CMC) { } // Gets blockchain data -const { createAlchemyWeb3 } = require("@alch/alchemy-web3"); +import { Network, Alchemy } from 'alchemy-sdk'; console.log("Connecting to HTTP RPC's"); -const web3layer1 = createAlchemyWeb3(API_L1_HTTP); -const web3layer2 = createAlchemyWeb3(API_L2_HTTP); +const web3layer1 = new Alchemy({apiKey: API_L1_KEY, network: Network.ETH_MAINNET}); +const web3layer2 = new Alchemy({apiKey: API_L2_KEY, network: Network.ARB_MAINNET}); // ENS stuff TODO: CONF_DISABLE_ENS const { ethers } = require("ethers"); -const provider = new ethers.providers.JsonRpcProvider(API_L1_HTTP); +const l1provider = new ethers.providers.AlchemyProvider("mainnet", API_L1_KEY); +const l2provider = new ethers.providers.AlchemyProvider("arbitrum", API_L2_KEY); // Smart contract event stuff // https://arbiscan.io/address/0x35Bcf3c30594191d53231E4FF333E8A770453e40#events @@ -89,17 +90,17 @@ if (!CONF_SIMPLE_MODE) { BondingManagerTargetJson = fs.readFileSync('src/abi/BondingManagerTarget.json'); BondingManagerTargetAbi = JSON.parse(BondingManagerTargetJson); BondingManagerProxyAddr = "0x35Bcf3c30594191d53231E4FF333E8A770453e40"; - bondingManagerContract = new web3layer2.eth.Contract(BondingManagerTargetAbi.abi, BondingManagerProxyAddr); + bondingManagerContract = new ethers.Contract(BondingManagerProxyAddr, BondingManagerTargetAbi.abi, l2provider); // Listen for events on the ticket broker contract TicketBrokerTargetJson = fs.readFileSync('src/abi/TicketBrokerTarget.json'); TicketBrokerTargetAbi = JSON.parse(TicketBrokerTargetJson); TicketBrokerTargetAddr = "0xa8bB618B1520E284046F3dFc448851A1Ff26e41B"; - ticketBrokerContract = new web3layer2.eth.Contract(TicketBrokerTargetAbi.abi, TicketBrokerTargetAddr); + ticketBrokerContract = new ethers.Contract(TicketBrokerTargetAddr, TicketBrokerTargetAbi.abi, l2provider); // Listen for events on the rounds manager contract RoundsManagerTargetJson = fs.readFileSync('src/abi/RoundsManagerTarget.json'); RoundsManagerTargetAbi = JSON.parse(RoundsManagerTargetJson); RoundsManagerTargetAddr = "0xdd6f56DcC28D3F5f27084381fE8Df634985cc39f"; - roundsManagerContract = new web3layer2.eth.Contract(RoundsManagerTargetAbi.abi, RoundsManagerTargetAddr); + roundsManagerContract = new ethers.Contract(RoundsManagerTargetAddr, RoundsManagerTargetAbi.abi, l2provider); } /* @@ -136,7 +137,7 @@ const getBlock = async function (blockNumber) { } } // Else get it and cache it - const thisBlock = await web3layer2.eth.getBlock(blockNumber); + const thisBlock = await web3layer2.core.getBlock(blockNumber); console.log("Caching new block " + thisBlock.number + " mined at " + thisBlock.timestamp); const blockObj = { @@ -216,14 +217,14 @@ let totalStakeDataPoint = []; apiRouter.post("/getAllMonthlyStats", async (req, res) => { try { const { smartUpdate } = req.body; - if (smartUpdate && req.session.user.ip) { + if (smartUpdate && req.session.user && req.session.user.ip) { if (alreadyHasMonthlyStatRefresh[req.session.user.ip]) { res.send({ noop: true }); return; } } res.send(monthlyStatCache); - if (req.session.user.ip) { + if (req.session.user && req.session.user.ip) { alreadyHasMonthlyStatRefresh[req.session.user.ip] = true; } } catch (err) { @@ -250,14 +251,14 @@ apiRouter.get("/getAllTotalStakes", async (req, res) => { apiRouter.post("/getAllUpdateEvents", async (req, res) => { try { const { smartUpdate } = req.body; - if (smartUpdate && req.session.user.ip) { + if (smartUpdate && req.session.user && req.session.user.ip) { if (alreadyHasUpdateRefresh[req.session.user.ip]) { res.send({ noop: true }); return; } } res.send(updateEventCache); - if (req.session.user.ip) { + if (req.session.user && req.session.user.ip) { alreadyHasAnyRefresh[req.session.user.ip] = true; alreadyHasUpdateRefresh[req.session.user.ip] = true; } @@ -269,14 +270,14 @@ apiRouter.post("/getAllUpdateEvents", async (req, res) => { apiRouter.post("/getAllRewardEvents", async (req, res) => { try { const { smartUpdate } = req.body; - if (smartUpdate && req.session.user.ip) { + if (smartUpdate && req.session.user &&req.session.user.ip) { if (alreadyHasRewardRefresh[req.session.user.ip]) { res.send({ noop: true }); return; } } res.send(rewardEventCache); - if (req.session.user.ip) { + if (req.session.user && req.session.user.ip) { alreadyHasAnyRefresh[req.session.user.ip] = true; alreadyHasRewardRefresh[req.session.user.ip] = true; } @@ -288,14 +289,14 @@ apiRouter.post("/getAllRewardEvents", async (req, res) => { apiRouter.post("/getAllClaimEvents", async (req, res) => { try { const { smartUpdate } = req.body; - if (smartUpdate && req.session.user.ip) { + if (smartUpdate && req.session.user && req.session.user.ip) { if (alreadyHasClaimRefresh[req.session.user.ip]) { res.send({ noop: true }); return; } } res.send(claimEventCache); - if (req.session.user.ip) { + if (req.session.user && req.session.user.ip) { alreadyHasAnyRefresh[req.session.user.ip] = true; alreadyHasClaimRefresh[req.session.user.ip] = true; } @@ -307,14 +308,14 @@ apiRouter.post("/getAllClaimEvents", async (req, res) => { apiRouter.post("/getAllWithdrawStakeEvents", async (req, res) => { try { const { smartUpdate } = req.body; - if (smartUpdate && req.session.user.ip) { + if (smartUpdate && req.session.user && req.session.user.ip) { if (alreadyHasWithdrawStakeRefresh[req.session.user.ip]) { res.send({ noop: true }); return; } } res.send(withdrawStakeEventCache); - if (req.session.user.ip) { + if (req.session.user && req.session.user.ip) { alreadyHasAnyRefresh[req.session.user.ip] = true; alreadyHasWithdrawStakeRefresh[req.session.user.ip] = true; } @@ -326,14 +327,14 @@ apiRouter.post("/getAllWithdrawStakeEvents", async (req, res) => { apiRouter.post("/getAllWithdrawFeesEvents", async (req, res) => { try { const { smartUpdate } = req.body; - if (smartUpdate && req.session.user.ip) { + if (smartUpdate && req.session.user && req.session.user.ip) { if (alreadyHasWithdrawFeesRefresh[req.session.user.ip]) { res.send({ noop: true }); return; } } res.send(withdrawFeesEventCache); - if (req.session.user.ip) { + if (req.session.user && req.session.user.ip) { alreadyHasAnyRefresh[req.session.user.ip] = true; alreadyHasWithdrawFeesRefresh[req.session.user.ip] = true; } @@ -345,14 +346,14 @@ apiRouter.post("/getAllWithdrawFeesEvents", async (req, res) => { apiRouter.post("/getAllTransferTicketEvents", async (req, res) => { try { const { smartUpdate } = req.body; - if (smartUpdate && req.session.user.ip) { + if (smartUpdate && req.session.user && req.session.user.ip) { if (alreadyHasTransferTicketRefresh[req.session.user.ip]) { res.send({ noop: true }); return; } } res.send(transferTicketEventCache); - if (req.session.user.ip) { + if (req.session.user && req.session.user.ip) { alreadyHasAnyRefresh[req.session.user.ip] = true; alreadyHasTransferTicketRefresh[req.session.user.ip] = true; } @@ -364,14 +365,14 @@ apiRouter.post("/getAllTransferTicketEvents", async (req, res) => { apiRouter.post("/getAllRedeemTicketEvents", async (req, res) => { try { const { smartUpdate } = req.body; - if (smartUpdate && req.session.user.ip) { + if (smartUpdate && req.session.user && req.session.user.ip) { if (alreadyHasRedeemTicketRefresh[req.session.user.ip]) { res.send({ noop: true }); return; } } res.send(redeemTicketEventCache); - if (req.session.user.ip) { + if (req.session.user && req.session.user.ip) { alreadyHasAnyRefresh[req.session.user.ip] = true; alreadyHasRedeemTicketRefresh[req.session.user.ip] = true; } @@ -391,14 +392,14 @@ apiRouter.get("/getAllWinningTickets", async (req, res) => { apiRouter.post("/getAllActivateEvents", async (req, res) => { try { const { smartUpdate } = req.body; - if (smartUpdate && req.session.user.ip) { + if (smartUpdate && req.session.user && req.session.user.ip) { if (alreadyHasActivateRefresh[req.session.user.ip]) { res.send({ noop: true }); return; } } res.send(activateEventCache); - if (req.session.user.ip) { + if (req.session.user && req.session.user.ip) { alreadyHasAnyRefresh[req.session.user.ip] = true; alreadyHasActivateRefresh[req.session.user.ip] = true; } @@ -410,14 +411,14 @@ apiRouter.post("/getAllActivateEvents", async (req, res) => { apiRouter.post("/getAllUnbondEvents", async (req, res) => { try { const { smartUpdate } = req.body; - if (smartUpdate && req.session.user.ip) { + if (smartUpdate && req.session.user && req.session.user.ip) { if (alreadyHasUnbondRefresh[req.session.user.ip]) { res.send({ noop: true }); return; } } res.send(unbondEventCache); - if (req.session.user.ip) { + if (req.session.user && req.session.user.ip) { alreadyHasAnyRefresh[req.session.user.ip] = true; alreadyHasUnbondRefresh[req.session.user.ip] = true; } @@ -429,14 +430,14 @@ apiRouter.post("/getAllUnbondEvents", async (req, res) => { apiRouter.post("/getAllStakeEvents", async (req, res) => { try { const { smartUpdate } = req.body; - if (smartUpdate && req.session.user.ip) { + if (smartUpdate && req.session.user && req.session.user.ip) { if (alreadyHasStakeRefresh[req.session.user.ip]) { res.send({ noop: true }); return; } } res.send(stakeEventCache); - if (req.session.user.ip) { + if (req.session.user && req.session.user.ip) { alreadyHasAnyRefresh[req.session.user.ip] = true; alreadyHasStakeRefresh[req.session.user.ip] = true; } @@ -447,7 +448,7 @@ apiRouter.post("/getAllStakeEvents", async (req, res) => { apiRouter.get("/hasAnyRefresh", async (req, res) => { try { - if (req.session.user.ip) { + if (req.session.user && req.session.user.ip) { console.log(req.session.user.ip + " is checking for new Events"); if (alreadyHasAnyRefresh[req.session.user.ip]) { console.log(req.session.user.ip + " is still up to date"); @@ -1624,7 +1625,7 @@ const syncEvents = function (toBlock) { isEventSyncing = true; let lastTxSynced = 0; // Then do a sync from last found until latest known - bondingManagerContract.getPastEvents("allEvents", { fromBlock: lastBlockEvents + 1, toBlock: toBlock }, async (error, events) => { + web3layer2.core.getLogs({ address: "0x35Bcf3c30594191d53231E4FF333E8A770453e40", fromBlock: lastBlockEvents + 1, toBlock: toBlock }, async (error, events) => { try { if (error) { throw error @@ -1685,7 +1686,7 @@ const syncTickets = function (toBlock) { console.log("Starting sync process for Ticket Broker events for blocks " + lastBlockTickets + "->" + toBlock); isTicketSyncing = true; // Then do a sync from last found until latest known - ticketBrokerContract.getPastEvents("allEvents", { fromBlock: lastBlockTickets + 1, toBlock: toBlock }, async (error, events) => { + web3layer2.core.getLogs({ address: "0xa8bB618B1520E284046F3dFc448851A1Ff26e41B", fromBlock: lastBlockTickets + 1, toBlock: toBlock }, async (error, events) => { try { if (error) { throw error @@ -1736,7 +1737,7 @@ const syncRounds = function (toBlock) { console.log("Starting sync process for Rounds Manager events for blocks " + lastBlockRounds + "->" + toBlock); isRoundSyncing = true; // Then do a sync from last found until latest known - roundsManagerContract.getPastEvents("allEvents", { fromBlock: lastBlockRounds + 1, toBlock: toBlock }, async (error, events) => { + web3layer2.core.getLogs({ address: "0xdd6f56DcC28D3F5f27084381fE8Df634985cc39f", fromBlock: lastBlockRounds + 1, toBlock: toBlock }, async (error, events) => { try { if (error) { throw error @@ -2044,12 +2045,12 @@ const handleSync = async function () { console.log('Starting new sync cycle #' + cycle); isSyncing = true; // Get latest blocks in chain - var latestBlock = await web3layer1.eth.getBlockNumber(); + var latestBlock = await web3layer1.core.getBlockNumber(); if (latestBlock > latestL1Block) { latestL1Block = latestBlock; console.log("Latest L1 Eth block changed to " + latestL1Block); } - latestBlock = await web3layer2.eth.getBlockNumber(); + latestBlock = await web3layer2.core.getBlockNumber(); if (latestBlock > latestBlockInChain) { latestBlockInChain = latestBlock; console.log("Latest L2 Eth block changed to " + latestBlockInChain); @@ -2141,6 +2142,7 @@ const handleSync = async function () { return; } catch (err) { + console.log(err); hasError = false; console.log("Error while syncing. Retrying in 30 seconds"); console.log("latestBlockInChain " + latestBlockInChain); @@ -2280,8 +2282,9 @@ let serviceUriFeeCostL2 = 0; // Queries Alchemy for block info and gas fees const parseL1Blockchain = async function () { - const l1Wei = await web3layer1.eth.getGasPrice(); - l1block = await web3layer1.eth.getBlockNumber(); + try { + const l1Wei = await web3layer1.core.getGasPrice(); + l1block = await web3layer1.core.getBlockNumber(); l1Gwei = l1Wei / 1000000000; redeemRewardCostL1 = (redeemRewardGwei * l1Gwei) / 1000000000; claimTicketCostL1 = (claimTicketGwei * l1Gwei) / 1000000000; @@ -2289,10 +2292,14 @@ const parseL1Blockchain = async function () { stakeFeeCostL1 = (stakeFeeGwei * l1Gwei) / 1000000000; commissionFeeCostL1 = (commissionFeeGwei * l1Gwei) / 1000000000; serviceUriFeeCostL1 = (serviceUriFee * l1Gwei) / 1000000000; + } catch (err) { + console.log(err); + } } const parseL2Blockchain = async function () { - const l2Wei = await web3layer2.eth.getGasPrice(); - l2block = await web3layer2.eth.getBlockNumber(); + try { + const l2Wei = await web3layer2.core.getGasPrice(); + l2block = await web3layer2.core.getBlockNumber(); l2Gwei = l2Wei / 1000000000; redeemRewardCostL2 = (redeemRewardGwei * l2Gwei) / 1000000000; claimTicketCostL2 = (claimTicketGwei * l2Gwei) / 1000000000; @@ -2300,10 +2307,17 @@ const parseL2Blockchain = async function () { stakeFeeCostL2 = (stakeFeeGwei * l2Gwei) / 1000000000; commissionFeeCostL2 = (commissionFeeGwei * l2Gwei) / 1000000000; serviceUriFeeCostL2 = (serviceUriFee * l2Gwei) / 1000000000; + } catch (err) { + console.log(err); + } + } const parseEthBlockchain = async function () { console.log("Getting new blockchain data"); - await Promise.all([parseL1Blockchain(), parseL2Blockchain()]); + const l1Promise = parseL1Blockchain(); + const l2Promise = parseL2Blockchain(); + await Promise.all([l1Promise, l2Promise]); + console.log("done getting blockchain data"); } // Exports gas fees and contract prices @@ -2840,8 +2854,8 @@ apiRouter.get("/grafana", async (req, res) => { const now = new Date().getTime(); // Update blockchain data if the cached data has expired if (now - arbGet > CONF_TIMEOUT_ALCHEMY) { - await parseEthBlockchain(); - arbGet = now; +arbGet = now; +await parseEthBlockchain(); } // Update coin prices once their data has expired if (now - cmcPriceGet > CONF_TIMEOUT_CMC) { @@ -2883,9 +2897,9 @@ apiRouter.get("/prometheus/:orchAddr", async (req, res) => { const now = new Date().getTime(); // Update blockchain data if the cached data has expired if (now - arbGet > CONF_TIMEOUT_ALCHEMY) { - await parseEthBlockchain(); - arbGet = now; - } +arbGet = now; +await parseEthBlockchain(); + } // Update coin prices once their data has expired if (now - cmcPriceGet > CONF_TIMEOUT_CMC) { await parseCmc(); @@ -3061,7 +3075,7 @@ const getEnsDomain = async function (addr) { } } // Else get it and cache it - const ensDomain = await provider.lookupAddress(addr.toLowerCase()); + const ensDomain = await l1provider.lookupAddress(addr.toLowerCase()); let ensObj; if (!ensDomain) { ensObj = { @@ -3106,7 +3120,7 @@ const getEnsInfo = async function (addr) { } } // Else get it and cache it - const resolver = await provider.getResolver(addr); + const resolver = await l1provider.getResolver(addr); const description = await resolver.getText("description"); const url = await resolver.getText("url"); const avatar = await resolver.getAvatar(); @@ -3284,4 +3298,4 @@ apiRouter.get("/getAllOrchScores", async (req, res) => { }); -export default apiRouter; \ No newline at end of file +export default apiRouter; diff --git a/backend/src/server.js b/backend/src/server.js index da9772a..0e0013e 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -2,7 +2,7 @@ import express from 'express'; import mongoose from 'mongoose'; import session from "express-session"; -import connectStore from "connect-mongo"; +import MongoStore from "connect-mongo"; import { userRouter, sessionRouter, livepeerRouter } from './routes/index'; import { NODE_PORT, NODE_ENV, MONGO_URI, SESS_NAME, SESS_SECRET, @@ -15,20 +15,22 @@ const { NODE_ENV: mode } = process.env; (async () => { try { // Make DB connection if needed + let clientP; if (!CONF_SIMPLE_MODE && !CONF_DISABLE_DB){ if (mode == "production"){ - await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useFindAndModify: false}); + clientP = mongoose.connect(MONGO_URI, { useNewUrlParser: true}).then(m => m.connection.getClient()); }else if (mode == "development"){ - await mongoose.connect(MONGO_URI_DEV, { useNewUrlParser: true, useFindAndModify: false}); + clientP = mongoose.connect(MONGO_URI_DEV, { useNewUrlParser: true}).then(m => m.connection.getClient()); }else if (mode == "local"){ - await mongoose.connect(MONGO_URI_LOCAL, { useNewUrlParser: true, useFindAndModify: false}); + clientP = mongoose.connect(MONGO_URI_LOCAL, { useNewUrlParser: true}).then(m => m.connection.getClient()); }else{ - await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useFindAndModify: false}); + clientP = mongoose.connect(MONGO_URI, { useNewUrlParser: true}).then(m => m.connection.getClient()); } console.log('MongoDB connected on ' + mode); }else{ console.log('Running without a database connection' ); } + // Web application framework const app = express(); app.disable('x-powered-by'); @@ -36,19 +38,16 @@ const { NODE_ENV: mode } = process.env; app.use(express.urlencoded({ extended: true })); app.use(express.json()); - let MongoStore; if (!CONF_SIMPLE_MODE && !CONF_DISABLE_DB){ - // Import session module - MongoStore = connectStore(session); // Declare session data app.use(session({ name: SESS_NAME, //TODO: change secret in config file secret: SESS_SECRET, //define where to store them - store: new MongoStore({ - mongooseConnection: mongoose.connection, - collection: 'session', + store: MongoStore.create({ + clientPromise: clientP, + collectionName: 'session', ttl: parseInt(SESS_LIFETIME) / 1000, }), saveUninitialized: false, @@ -88,4 +87,4 @@ const { NODE_ENV: mode } = process.env; } catch (err) { console.log(err); } -})(); \ No newline at end of file +})(); diff --git a/package.json b/package.json index 80b5bcb..2c69d88 100644 --- a/package.json +++ b/package.json @@ -3,31 +3,32 @@ "version": "0.1.0", "private": true, "dependencies": { - "@mantine/core": "^4.1.3", - "@mantine/dates": "^4.1.3", - "@mantine/form": "^4.1.3", - "@mantine/hooks": "^4.1.3", - "@mantine/modals": "^4.1.3", + "@emotion/react": "^11.10.5", + "@mantine/core": "^4.2.12", + "@mantine/dates": "^4.2.12", + "@mantine/form": "^4.2.12", + "@mantine/hooks": "^4.2.12", + "@mantine/modals": "^4.2.12", "@mantine/next": "^4.1.3", - "@mantine/notifications": "^4.1.3", + "@mantine/notifications": "^4.2.12", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", - "dayjs": "^1.11.1", + "dayjs": "^1.11.7", "ethers": "^5.4.4", "http": "^0.0.1-security", "https": "^1.0.0", "md5": "^2.3.0", - "react": "^17.0.2", + "react": "^18.2.0", "react-circular-progressbar": "^2.0.4", - "react-dom": "^17.0.2", + "react-dom": "^18.2.0", "react-indiana-drag-scroll": "^2.1.0", "react-markdown": "^7.1.1", "react-paginate": "^8.1.2", "react-redux": "^7.2.6", "react-retro-hit-counter": "^1.0.1", "react-router-dom": "^6.0.2", - "react-scripts": "3.2.0", + "react-scripts": "^5.0.1", "redux": "^4.1.2", "redux-thunk": "^2.4.1", "styled-components": "^5.3.3", diff --git a/src/style.css b/src/style.css index 3e6acef..0e246fb 100644 --- a/src/style.css +++ b/src/style.css @@ -29,7 +29,7 @@ body { sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - background-image: url("/background.jpg"); + background-image: url("../public/background.jpg"); background-repeat: no-repeat center center fixed; overflow-x: hidden; overflow-y: auto; @@ -750,4 +750,4 @@ svg { .main-container { height: calc(100vh - 60px); } -} \ No newline at end of file +}