diff --git a/.env.example b/.env.example index 2f46473e..c461c62a 100644 --- a/.env.example +++ b/.env.example @@ -9,3 +9,4 @@ AVAILABLE_LANGUAGE_GROUP_PAIRS='["en|pt"]' LINGUO_CONTRACT_ADDRESSES='{"":{"en|pt":["0x00...0000"],"en|es:["0x00..0000"]}}' USER_SETTINGS_API_BASE_URLS='{"": , ...}' JSON_RPC_URLS='{"": , ...}' +SUBGRAPH_PROJECT_NAMES ='{"100":"/","5":"..."}' diff --git a/.eslintrc.json b/.eslintrc.json index 8961ffb2..da1450f7 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,7 +5,8 @@ "plugin:react-hooks/recommended", "plugin:import/recommended", "plugin:import/react", - "plugin:security/recommended" + "plugin:security/recommended", + "prettier" ], "plugins": [ "react", @@ -28,7 +29,7 @@ "ecmaFeatures": { "jsx": true }, - "ecmaVersion": 2018, + "ecmaVersion": 2020, "sourceType": "module" }, "settings": { @@ -56,17 +57,6 @@ "argsIgnorePattern": "(^_+[0-9]*$)|([iI]gnored$)|(^ignored)" } ], - "no-console": [ - "error", - { - "allow": [ - "warn", - "error", - "info", - "debug" - ] - } - ], "security/detect-object-injection": "off", "security/detect-non-literal-fs-filename": "off", "import/extensions": [ diff --git a/netlify.toml b/netlify.toml index 16e78404..bf645b86 100644 --- a/netlify.toml +++ b/netlify.toml @@ -5,7 +5,10 @@ IPFS_GATEWAY_ADDRESS='https://ipfs.kleros.io' AVAILABLE_LANGUAGE_GROUP_PAIRS='["en|fr","en|es","en|pt","de|en","en|ru","en|ja","en|ko","en|tr","en|zh"]' USER_SETTINGS_API_BASE_URLS='{"1":"https://iioeu1ljqf.execute-api.us-east-2.amazonaws.com/mainnet","77":"https://xa1qxy0wp6.execute-api.us-east-2.amazonaws.com/sokol","100":"https://wcj9vww4nf.execute-api.us-east-2.amazonaws.com/xdai"}' - LINGUO_CONTRACT_ADDRESSES='{"1":{"de|en":["0x54f1f7b58c1Dc646a6c639C5d909B62652129b33","0x950f5AB863075C685227EfEDb41Ef167Be0099ad"],"en|es":["0x460f688f655610de5e3d92beb7892fa200b9060d","0xD8F8019c025C2Ba6745543D9a3C338DE1b98C103"],"en|fr":["0xFEe86325d5C96AE9E20EaBe7B157813c67bBccFA","0xf1A9589880DbF393F32A5b2d5a0054Fa10385074"],"en|ja":["0x5Cd1ff55282db11e526Fe13fA0F7C014f74acF11","0xc3162705Af0e10108FF837E450A14669b2711129"],"en|ko":["0xf8D4c1436ACA89A5DEEf36F06f662bAdfEb5AE63","0xFE721DD8Ac8e47A4228A6147A25C65136f213EaA"],"en|pt":["0x48032Ad54DB49d8E0d4f135A687b98E5731961f9","0x1c839fd542563182BFD0C337c8AaD1Be412F0fd2"],"en|ru":["0x0Bc3B51673dCdF3b45c628066761dA7331E8fBA2","0x464c84c41f3C25Ba5a75B006D8B20600A8777306"],"en|tr":["0xa3E4348BDDc32AfcedC5e088e0E21FD6154A0180","0x44863f5b7AAb7ceE181C0d84E244540125eF7AF7"],"en|zh":["0xaB0D90943A58b1A64c0171Ee8e743d9998Be6AC3","0xD67C12734dC12240a6324Db63ccd426964B71Fe7"]},"77":{"de|en":["0x8539cb2fB9186f3DcF8F9A0AeD3De395Fd6d9870"],"en|es":["0x5a9F70ED50e16010f0B9d5bb5a3EFdb42fEcaD23"],"en|fr":["0x9CA979a165EF741BF120A3FEbb4F5C083De40708"],"en|ja":["0x753d611eAdF324B1f23A3A417BceB08eeda80ec6"],"en|ko":["0xC578de969Dd2A2a08d6Bd5A4F7AaA9b1a2e6A395"],"en|pt":["0x457b2BAb823c772fA4457517C97E6BF13065c21D"],"en|ru":["0x6cD5BAb73135966008d969bE23Ff940e6fa3Bb87"],"en|tr":["0xa9426b235E12897A99B015CbcF4D064e86fde418"],"en|zh":["0x09CE835FaB6051a18b2FC11743c01DAAD3122f1F"]},"100":{"de|en":["0xc3162705Af0e10108FF837E450A14669b2711129"],"en|es":["0xA2BfFF0553DE7405781FE0C39C04A383F04b9C80"],"en|fr":["0x464c84c41f3C25Ba5a75B006D8B20600A8777306"],"en|ja":["0x852550982e0984F9CCeF18a7276D35AFDc30242c"],"en|ko":["0xD67C12734dC12240a6324Db63ccd426964B71Fe7"],"en|pt":["0xFE721DD8Ac8e47A4228A6147A25C65136f213EaA"],"en|ru":["0x44863f5b7AAb7ceE181C0d84E244540125eF7AF7"],"en|tr":["0x1D48a279966f37385b4AB963530C6dC813b3A8Df"],"en|zh":["0x0B928165A67df8254412483ae8C3b8cc7F2b4D36"]}}' + LINGUO_CONTRACT_ADDRESSES='{"1":{"de|en":["0x54f1f7b58c1Dc646a6c639C5d909B62652129b33","0x950f5AB863075C685227EfEDb41Ef167Be0099ad"],"en|es":["0x460f688f655610de5e3d92beb7892fa200b9060d","0xD8F8019c025C2Ba6745543D9a3C338DE1b98C103"],"en|fr":["0xFEe86325d5C96AE9E20EaBe7B157813c67bBccFA","0xf1A9589880DbF393F32A5b2d5a0054Fa10385074"],"en|ja":["0x5Cd1ff55282db11e526Fe13fA0F7C014f74acF11","0xc3162705Af0e10108FF837E450A14669b2711129"],"en|ko":["0xf8D4c1436ACA89A5DEEf36F06f662bAdfEb5AE63","0xFE721DD8Ac8e47A4228A6147A25C65136f213EaA"],"en|pt":["0x48032Ad54DB49d8E0d4f135A687b98E5731961f9","0x1c839fd542563182BFD0C337c8AaD1Be412F0fd2"],"en|ru":["0x0Bc3B51673dCdF3b45c628066761dA7331E8fBA2","0x464c84c41f3C25Ba5a75B006D8B20600A8777306"],"en|tr":["0xa3E4348BDDc32AfcedC5e088e0E21FD6154A0180","0x44863f5b7AAb7ceE181C0d84E244540125eF7AF7"],"en|zh":["0xaB0D90943A58b1A64c0171Ee8e743d9998Be6AC3","0xD67C12734dC12240a6324Db63ccd426964B71Fe7"]},"77":{"de|en":["0x8539cb2fB9186f3DcF8F9A0AeD3De395Fd6d9870"],"en|es":["0x5a9F70ED50e16010f0B9d5bb5a3EFdb42fEcaD23"],"en|fr":["0x9CA979a165EF741BF120A3FEbb4F5C083De40708"],"en|ja":["0x753d611eAdF324B1f23A3A417BceB08eeda80ec6"],"en|ko":["0xC578de969Dd2A2a08d6Bd5A4F7AaA9b1a2e6A395"],"en|pt":["0x457b2BAb823c772fA4457517C97E6BF13065c21D"],"en|ru":["0x6cD5BAb73135966008d969bE23Ff940e6fa3Bb87"],"en|tr":["0xa9426b235E12897A99B015CbcF4D064e86fde418"],"en|zh":["0x09CE835FaB6051a18b2FC11743c01DAAD3122f1F"]},"100":{"de|en":["0xc3162705Af0e10108FF837E450A14669b2711129"],"en|es":["0xA2BfFF0553DE7405781FE0C39C04A383F04b9C80"],"en|fr":["0x464c84c41f3C25Ba5a75B006D8B20600A8777306"],"en|ja":["0x852550982e0984F9CCeF18a7276D35AFDc30242c"],"en|ko":["0xD67C12734dC12240a6324Db63ccd426964B71Fe7"],"en|pt":["0xFE721DD8Ac8e47A4228A6147A25C65136f213EaA"],"en|ru":["0x44863f5b7AAb7ceE181C0d84E244540125eF7AF7"],"en|tr":["0x1D48a279966f37385b4AB963530C6dC813b3A8Df"],"en|zh":["0x0B928165A67df8254412483ae8C3b8cc7F2b4D36"]},"5":{"de|en":["0xDDFac50f2f9C60AD1b263C37253E7f157231dfF3"],"en|es":["0x5fB4B92c48dB367Dee88e34cA977Af0581D1a855"],"en|fr":["0x81F78a49E472B507B49ea7a01dF2760E1922dce4"],"en|ja":["0x9B926259A16F975f931E5587b5756EFAAC305B5f"],"en|ko":["0x558457cA2a4c22b9A50E8822FDf7927887D90D6c"],"en|pt":["0x3e05C4dEc668bd0A93668437577fea36Db77AB65"],"en|ru":["0xa0D5931DecB5Fb7265ac0417fDB6D2C39500F824"],"en|tr":["0x1bfEbB574A263D4d627ceF63d9fA23CD624E0e5c"],"en|zh":["0x5bd640A53E2E29984D405FCc0b1526c252A325D7"]}}' + JSON_RPC_URLS='{"100":"https://rpc.ankr.com/gnosis","1":"https://mainnet.infura.io/v3/","77":"https://sokol.poa.network","5":"https://goerli.infura.io/v3/"}' + SUBGRAPH_PROJECT_NAMES='{"100":"kleros/linguo-gnosis","5":"kleros/linguo-goerli"}' + [context.production.environment] PRODUCTION='true' diff --git a/package.json b/package.json index f4096b44..40389a3c 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,6 @@ "caniuse-lite": "1.0.30001436" }, "dependencies": { - "3box": "^1.19.1", "3id-resolver": "^1.0.1", "@ant-design/icons": "^4.3.0", "@ethersproject/address": "^5.0.5", @@ -41,13 +40,13 @@ "@kleros/react-components": "^6.0.0", "@loadable/component": "^5.12.0", "@reduxjs/toolkit": "^1.6.1", - "@rehooks/local-storage": "^2.2.3", + "@rehooks/local-storage": "^2.4.4", "@uniswap/sdk": "^3.0.3", "@web3-react/abstract-connector": "^6.0.7", - "@web3-react/core": "^6.1.1", + "@web3-react/core": "^6.1.9", "@web3-react/injected-connector": "^6.0.7", "@web3-react/network-connector": "^6.1.5", - "@web3-react/walletconnect-connector": "^6.2.4", + "@web3-react/walletconnect-connector": "^6.2.13", "antd": "~4.16.0", "browserslist": "^4.21.4", "clsx": "^1.1.0", @@ -57,13 +56,17 @@ "deep-equal": "^2.0.5", "deepmerge": "^4.2.2", "did-resolver": "^2.1.2", + "ethers": "^5.7.2", + "graphql": "^16.6.0", + "graphql-request": "^5.1.0", "history": "^4.10.1", "hoist-non-react-statics": "^3.3.2", - "immer": "^9.0.6", + "immer": "^9.0.19", "ipfs-http-client": "^42.0.0", "js-base64": "^3.6.0", "js-combinatorics": "^0.6.1", "localforage": "^1.10.0", + "moment": "^2.29.4", "nanoid": "^3.1.9", "normalize.css": "^8.0.1", "path-to-regexp": "^6.1.0", @@ -71,12 +74,13 @@ "react": "^17.0.2", "react-blockies": "^1.4.1", "react-dom": "^17.0.2", - "react-hot-loader": "^4.13.0", + "react-hot-loader": "^4.13.1", "react-is": "^17.0.2", "react-redux": "^7.2.5", "react-router": "^5.1.2", "react-router-dom": "^5.1.2", "react-titled": "^1.0.1", + "react-toastify": "^9.1.1", "react-visibility-sensor": "^5.1.1", "redux": "^4.1.1", "redux-persist": "^6.0.0", @@ -84,7 +88,8 @@ "redux-saga-thunk": "^0.7.3", "scroll-into-view-if-needed": "^2.2.25", "serialize-error": "^7.0.1", - "styled-components": "^5.3.1", + "styled-components": "^5.3.6", + "swr": "^2.0.3", "web3": "^1.5.2" }, "devDependencies": { @@ -106,7 +111,7 @@ "@types/react": "^16.9.23", "autoprefixer": "^9.7.4", "babel-eslint": "^10.1.0", - "babel-plugin-import": "^1.13.0", + "babel-plugin-import": "^1.13.6", "babel-plugin-styled-components": "^1.10.7", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", "cssnano": "^4.1.10", diff --git a/src/adapters/price-oracle/makerDAOFeed/getEthPriceUsd.js b/src/adapters/price-oracle/makerDAOFeed/getEthPriceUsd.js index bdda09b7..af70d1c9 100644 --- a/src/adapters/price-oracle/makerDAOFeed/getEthPriceUsd.js +++ b/src/adapters/price-oracle/makerDAOFeed/getEthPriceUsd.js @@ -1,11 +1,12 @@ import Web3 from 'web3'; -import { jsonRpcUrls } from '~/features/web3/supportedChains'; +import { RPC_URLS } from '~/consts/supportedChains'; + import abi from './assets/MedianEthUsd.json'; -const providerUrl = jsonRpcUrls[1]; +const providerUrl = RPC_URLS[1]; const provider = new Web3.providers.HttpProvider(providerUrl); const web3 = new Web3(provider); -const contract = new web3.eth.Contract(abi, '0x64de91f5a373cd4c28de3600cb34c7c6ce410c85'); +const contract = new web3.eth.Contract(abi, '0x64de91f5a373cd4c28de3600cb34c7c6ce410c85'); // https://mips.makerdao.com/mips/md-viewer#active-oracle-list async function getPriceFromLatestBlocks({ fromBlock }) { const events = await contract.getPastEvents('LogMedianPrice', { diff --git a/src/app/App.jsx b/src/app/App.jsx index f92f052c..43262c69 100644 --- a/src/app/App.jsx +++ b/src/app/App.jsx @@ -1,45 +1,23 @@ import React from 'react'; import { hot } from 'react-hot-loader'; -import t from 'prop-types'; import { createGlobalStyle, ThemeProvider } from 'styled-components'; -import { Web3ReactProvider } from '@web3-react/core'; -import Web3 from 'web3'; import theme from '~/features/ui/theme'; -import { useWatchLibrary, useWeb3ReactBootstrap } from '~/features/web3'; import MainRouter from './MainRouter'; +import Web3Provider from '../context/Web3Provider'; function App() { return ( - + - - - - + + - + ); } export default hot(module)(App); -function _Initializer({ children }) { - useWeb3ReactBootstrap(); - useWatchLibrary(); - - return children; -} - -_Initializer.propTypes = { - children: t.node, -}; - -const Initializer = React.memo(_Initializer); - -function getLibrary(provider) { - return new Web3(provider); -} - const GlobalStyle = createGlobalStyle` body { min-width: 24rem; diff --git a/src/app/Content.jsx b/src/app/Content.jsx index 90f52c95..16362d82 100644 --- a/src/app/Content.jsx +++ b/src/app/Content.jsx @@ -1,67 +1,26 @@ import React from 'react'; import t from 'prop-types'; import styled from 'styled-components'; -import { useDispatch, useSelector } from 'react-redux'; import { Layout } from 'antd'; import { nanoid } from 'nanoid'; -import { - selectLatestBlock, - subscribeToUpdates, - unsubscribeFromUpdates, - updateTransientNotifications, -} from '~/features/tasks/tasksSlice'; + import { subscribeToEthPrice, unsubscribeFromEthPrice } from '~/features/tokens/tokensSlice'; -import { selectAccount, selectChainId } from '~/features/web3/web3Slice'; +import { useWeb3 } from '~/hooks/useWeb3'; +import { useDispatch } from 'react-redux'; export default function Content({ children }) { - useTaskUpdatesSubscription(); useEthPricePolling(); return {children}; } -function useTaskUpdatesSubscription() { - const dispatch = useDispatch(); - - const account = useSelector(selectAccount); - const chainId = useSelector(selectChainId); - const latestBlock = useSelector(selectLatestBlock({ chainId, account })); - - const subscribe = React.useCallback( - () => dispatch(subscribeToUpdates({ chainId, account, fromBlock: latestBlock + 1 }, { meta: { groupId } })), - [dispatch, account, chainId, latestBlock] - ); - const unsubscribe = React.useCallback(() => dispatch(unsubscribeFromUpdates({}, { meta: { groupId } })), [dispatch]); - - React.useEffect(() => { - dispatch(updateTransientNotifications({ account, chainId })); - }, [dispatch, account, chainId]); - - React.useEffect(() => { - if (account) { - subscribe(); - - window.addEventListener('focus', subscribe); - window.addEventListener('blur', unsubscribe); - - return () => { - unsubscribe(); - - window.removeEventListener('focus', subscribe); - window.removeEventListener('blur', unsubscribe); - }; - } - }, [dispatch, account, subscribe, unsubscribe]); -} - const groupId = nanoid(10); const _1_MINUTE = 60 * 1000; function useEthPricePolling({ interval = _1_MINUTE } = {}) { + const { chainId } = useWeb3(); const dispatch = useDispatch(); - const chainId = useSelector(selectChainId); - const subscribe = React.useCallback( () => dispatch(subscribeToEthPrice({ chainId, interval, immediate: true }, { meta: { groupId } })), [dispatch, chainId, interval] diff --git a/src/app/MainRouter.jsx b/src/app/MainRouter.jsx index 4538fea9..9a13a6db 100644 --- a/src/app/MainRouter.jsx +++ b/src/app/MainRouter.jsx @@ -1,23 +1,26 @@ import React from 'react'; -import t from 'prop-types'; -import { useDispatch, useSelector } from 'react-redux'; +import { SWRConfig } from 'swr'; +import { request } from 'graphql-request'; + import { Redirect, Route, Switch } from 'react-router-dom'; import loadable from '@loadable/component'; import { Layout } from 'antd'; import { ConnectedRouter } from 'connected-react-router'; -import { Alert, Spin } from '~/adapters/antd'; -import { getNetworkName, useSwitchToChainFromUrl } from '~/features/web3'; -import { getCounterPartyChainId, isSupportedChain, isSupportedSideChain } from '~/features/web3/supportedChains'; -import Web3ErrorAlert from '~/features/web3/Web3ErrorAlert'; -import { selectChainId, switchChain } from '~/features/web3/web3Slice'; -import { WarningIcon } from '~/shared/icons'; -import Button from '~/shared/Button'; -import Footer from '~/shared/Footer'; -import { DrawerMenu } from '~/shared/Menu'; -import Navbar from '~/shared/Navbar'; +import { Spin } from '~/adapters/antd'; +import * as r from './routes'; + +import Web3ErrorAlert from '~/components/Web3ErrorAlert'; + +import Footer from '~/layout/Footer'; +import Navbar from '~/layout/Header/Navbar'; +import { DrawerMenu } from '~/layout/Header/Menu'; + import { history } from '~/store'; import Content from './Content'; -import * as r from './routes'; +import Web3ConnectionManager from '~/components/Web3ConnectionManager'; +import GlobalWarnings from '~/components/GlobalWarnings'; +import TranslatorSkillsProvider from '~/context/TranslatorSkillsProvider'; +import { useWeb3 } from '~/hooks/useWeb3'; const fallback = ; @@ -29,125 +32,73 @@ const RequesterDashboard = loadable(() => import('~/pages/RequesterDashboard'), const TranslationRequest = loadable(() => import('~/pages/TranslationRequest'), { fallback }); const TranslationDetails = loadable(() => import('~/pages/TranslationDetails'), { fallback }); +const SUBGRAPH_NAMES = JSON.parse(process.env.SUBGRAPH_PROJECT_NAMES); +const fetcherBuilder = + url => + ({ query, variables }) => { + return request(url, query, variables); + }; + export default function MainRouter() { + const { chainId } = useWeb3(); + return ( - - - - - p.theme.color.background.default}; - `} - > - -
+ + + + + p.theme.color.background.default}; `} - >
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -