From 6887d71a057baf3c0029da918efbd254364bd95d Mon Sep 17 00:00:00 2001 From: alcercu Date: Mon, 19 Sep 2022 11:48:00 +0200 Subject: [PATCH 1/6] feat(web): add web3 boilerplate --- web/package.json | 8 ++- web/src/connectors/injected.ts | 6 +++ web/src/consts/supportedChains.ts | 12 +++++ web/src/context/Web3Provider.tsx | 15 ++++++ web/src/hooks/useConnect.ts | 46 ++++++++++++++++ web/src/hooks/useWeb3.ts | 4 ++ web/tsconfig.json | 6 +++ yarn.lock | 88 +++++++++++++++++++++++++++++-- 8 files changed, 180 insertions(+), 5 deletions(-) create mode 100644 web/src/connectors/injected.ts create mode 100644 web/src/consts/supportedChains.ts create mode 100644 web/src/context/Web3Provider.tsx create mode 100644 web/src/hooks/useConnect.ts create mode 100644 web/src/hooks/useWeb3.ts diff --git a/web/package.json b/web/package.json index 674a83f91..663d950b7 100644 --- a/web/package.json +++ b/web/package.json @@ -8,8 +8,10 @@ "license": "MIT", "alias": { "src": "./src", + "utils": "./src/utils", "assets": "./src/assets", "components": "./src/components", + "connectors": "./src/connectors", "context": "./src/context", "layout": "./src/layout", "consts": "./src/consts", @@ -57,16 +59,20 @@ "@graphql-codegen/typescript-operations": "^2.5.3", "@kleros/kleros-v2-contracts": "workspace:^", "@kleros/ui-components-library": "^1.7.0", + "@web3-react/core": "^6.1.9", + "@web3-react/injected-connector": "^6.0.7", + "@web3-react/types": "^6.0.7", "chart.js": "^3.9.1", "chartjs-adapter-moment": "^1.0.0", "core-js": "^3.21.1", - "ethers": "^5.6.5", + "ethers": "^5.7.0", "graphql": "^16.4.0", "graphql-request": "^4.2.0", "moment": "^2.29.4", "react": "^18.2.0", "react-chartjs-2": "^4.3.1", "react-dom": "^18.2.0", + "react-error-boundary": "^3.1.4", "react-is": "^18.2.0", "react-router-dom": "6", "styled-components": "^5.3.5", diff --git a/web/src/connectors/injected.ts b/web/src/connectors/injected.ts new file mode 100644 index 000000000..ecebb57a5 --- /dev/null +++ b/web/src/connectors/injected.ts @@ -0,0 +1,6 @@ +import { InjectedConnector } from "@web3-react/injected-connector"; +import { SUPPORTED_CHAINIDS } from "consts/supportedChains"; + +export const injected = new InjectedConnector({ + supportedChainIds: SUPPORTED_CHAINIDS, +}); diff --git a/web/src/consts/supportedChains.ts b/web/src/consts/supportedChains.ts new file mode 100644 index 000000000..6fbb8dd99 --- /dev/null +++ b/web/src/consts/supportedChains.ts @@ -0,0 +1,12 @@ +export const SUPPORTED_CHAINS = { + 421611: { + chainName: "Arbitrum Rinkeby", + nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 }, + rpcUrls: ["https://rinkeby.arbitrum.io/rpc"], + blockExplorerUrls: ["https://testnet.arbiscan.io/"], + }, +}; + +export const SUPPORTED_CHAINIDS = Object.keys(SUPPORTED_CHAINS).map((x) => + parseInt(x) +); diff --git a/web/src/context/Web3Provider.tsx b/web/src/context/Web3Provider.tsx new file mode 100644 index 000000000..5ec98ea1a --- /dev/null +++ b/web/src/context/Web3Provider.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import { Web3ReactProvider } from "@web3-react/core"; +import { + Web3Provider as EthersProvider, + ExternalProvider, +} from "@ethersproject/providers"; + +const getLibrary = (provider: ExternalProvider): EthersProvider => + new EthersProvider(provider); + +const Web3Provider: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => {children} ; + +export default Web3Provider; diff --git a/web/src/hooks/useConnect.ts b/web/src/hooks/useConnect.ts new file mode 100644 index 000000000..bfbec44c4 --- /dev/null +++ b/web/src/hooks/useConnect.ts @@ -0,0 +1,46 @@ +import { useEffect, useState } from "react"; +import { useWeb3React, UnsupportedChainIdError } from "@web3-react/core"; +import { injected } from "connectors/injected"; + +export const useConnect = () => { + const { activate, error } = useWeb3React(); + const [connecting, setConnecting] = useState(false); + const [activationError, setActivationError] = useState(); + + const activateInjected = () => { + setConnecting(true); + activate(injected, undefined, true) + .catch((error) => setActivationError(error)) + .finally(() => setConnecting(false)); + }; + + const activateInjectedWithAccountsCheck = (accounts: string[]) => + accounts.length > 0 && activateInjected(); + + useEffect(() => { + const { ethereum } = window as any; + if (ethereum && ethereum.on) { + ethereum.on("chainChanged", activateInjected); + ethereum.on("accountsChanged", activateInjectedWithAccountsCheck); + return () => { + if (ethereum.removeListener) { + ethereum.removeListener("chainChanged", activateInjected); + ethereum.removeListener( + "accountsChanged", + activateInjectedWithAccountsCheck + ); + } + }; + } else return; + }); + + useEffect(() => { + if ( + activationError && + activationError.name === UnsupportedChainIdError.name + ) + throw activationError; + }, [activationError]); + + return { activate: activateInjected, connecting, error }; +}; diff --git a/web/src/hooks/useWeb3.ts b/web/src/hooks/useWeb3.ts new file mode 100644 index 000000000..300fdadbf --- /dev/null +++ b/web/src/hooks/useWeb3.ts @@ -0,0 +1,4 @@ +import { Web3Provider } from "@ethersproject/providers"; +import { useWeb3React } from "@web3-react/core"; + +export const useWeb3 = () => useWeb3React(); diff --git a/web/tsconfig.json b/web/tsconfig.json index 53a4c41ed..0af321879 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -8,12 +8,18 @@ "src*": [ "./src*" ], + "utils*": [ + "./src/utils*" + ], "assets*": [ "./src/assets*" ], "components*": [ "./src/components*" ], + "connectors*": [ + "./src/connectors*" + ], "context*": [ "./src/context*" ], diff --git a/yarn.lock b/yarn.lock index 9de2df84d..9d594330b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -641,6 +641,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.12.5": + version: 7.19.0 + resolution: "@babel/runtime@npm:7.19.0" + dependencies: + regenerator-runtime: ^0.13.4 + checksum: fa69c351bb05e1db3ceb9a02fdcf620c234180af68cdda02152d3561015f6d55277265d3109815992f96d910f3db709458cae4f8df1c3def66f32e0867d82294 + languageName: node + linkType: hard + "@babel/template@npm:^7.16.7": version: 7.16.7 resolution: "@babel/template@npm:7.16.7" @@ -1583,7 +1592,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/keccak256@npm:5.7.0, @ethersproject/keccak256@npm:^5.7.0": +"@ethersproject/keccak256@npm:5.7.0, @ethersproject/keccak256@npm:^5.0.0-beta.130, @ethersproject/keccak256@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/keccak256@npm:5.7.0" dependencies: @@ -2911,6 +2920,9 @@ __metadata: "@typescript-eslint/eslint-plugin": ^5.33.1 "@typescript-eslint/parser": ^5.33.1 "@typescript-eslint/utils": ^5.29.0 + "@web3-react/core": ^6.1.9 + "@web3-react/injected-connector": ^6.0.7 + "@web3-react/types": ^6.0.7 chart.js: ^3.9.1 chartjs-adapter-moment: ^1.0.0 core-js: ^3.21.1 @@ -2923,7 +2935,7 @@ __metadata: eslint-plugin-react-hooks: ^4.6.0 eslint-plugin-security: ^1.4.0 eslint-utils: ^3.0.0 - ethers: ^5.6.5 + ethers: ^5.7.0 graphql: ^16.4.0 graphql-request: ^4.2.0 lru-cache: ^7.8.0 @@ -2933,6 +2945,7 @@ __metadata: react: ^18.2.0 react-chartjs-2: ^4.3.1 react-dom: ^18.2.0 + react-error-boundary: ^3.1.4 react-is: ^18.2.0 react-router-dom: 6 styled-components: ^5.3.5 @@ -5451,6 +5464,48 @@ __metadata: languageName: node linkType: hard +"@web3-react/abstract-connector@npm:^6.0.7": + version: 6.0.7 + resolution: "@web3-react/abstract-connector@npm:6.0.7" + dependencies: + "@web3-react/types": ^6.0.7 + checksum: fa8b0ea3586e248da84756979dd5c5f6ddc2d1657a1698de63a6d0e38035c4b122841bd0e6b00f1a6f1825614b22f6552a584cafec3470ca6fbc2e127ab30b2a + languageName: node + linkType: hard + +"@web3-react/core@npm:^6.1.9": + version: 6.1.9 + resolution: "@web3-react/core@npm:6.1.9" + dependencies: + "@ethersproject/keccak256": ^5.0.0-beta.130 + "@web3-react/abstract-connector": ^6.0.7 + "@web3-react/types": ^6.0.7 + tiny-invariant: ^1.0.6 + tiny-warning: ^1.0.3 + peerDependencies: + react: ">=16.8" + checksum: b7f3ad62812fb0d000c50447f5b22329ce03942cc681cf01038a450872f79252445083a7fb325c7daf2b4de1f6f89e0298a7a54e0cea0b05872c37d64492af8b + languageName: node + linkType: hard + +"@web3-react/injected-connector@npm:^6.0.7": + version: 6.0.7 + resolution: "@web3-react/injected-connector@npm:6.0.7" + dependencies: + "@web3-react/abstract-connector": ^6.0.7 + "@web3-react/types": ^6.0.7 + tiny-warning: ^1.0.3 + checksum: 867a634a12326f33d10eead26f557fe505942ec42f5586d0756eb864fa345187b255a7d9b78bd555eb2395f962e9615731bcfefe6cf07ef9971a7fca151377f8 + languageName: node + linkType: hard + +"@web3-react/types@npm:^6.0.7": + version: 6.0.7 + resolution: "@web3-react/types@npm:6.0.7" + checksum: 29ae264ae4a126e520a64a4b466ea712ee09b934f3fe13b957e09269249ffc882712cf11fff12c1c19dd66e38503075b4adf5e9d5f588ea0dd31bbedf6fa9a42 + languageName: node + linkType: hard + "@webassemblyjs/ast@npm:1.11.1": version: 1.11.1 resolution: "@webassemblyjs/ast@npm:1.11.1" @@ -11465,7 +11520,7 @@ __metadata: languageName: node linkType: hard -"ethers@npm:^5.0.0, ethers@npm:^5.0.1, ethers@npm:^5.0.2, ethers@npm:^5.5.2, ethers@npm:^5.5.3, ethers@npm:^5.6.5, ethers@npm:^5.6.7": +"ethers@npm:^5.0.0, ethers@npm:^5.0.1, ethers@npm:^5.0.2, ethers@npm:^5.5.2, ethers@npm:^5.5.3, ethers@npm:^5.6.7": version: 5.6.8 resolution: "ethers@npm:5.6.8" dependencies: @@ -11503,7 +11558,7 @@ __metadata: languageName: node linkType: hard -"ethers@npm:^5.0.3": +"ethers@npm:^5.0.3, ethers@npm:^5.7.0": version: 5.7.0 resolution: "ethers@npm:5.7.0" dependencies: @@ -19711,6 +19766,17 @@ __metadata: languageName: node linkType: hard +"react-error-boundary@npm:^3.1.4": + version: 3.1.4 + resolution: "react-error-boundary@npm:3.1.4" + dependencies: + "@babel/runtime": ^7.12.5 + peerDependencies: + react: ">=16.13.1" + checksum: f36270a5d775a25c8920f854c0d91649ceea417b15b5bc51e270a959b0476647bb79abb4da3be7dd9a4597b029214e8fe43ea914a7f16fa7543c91f784977f1b + languageName: node + linkType: hard + "react-error-overlay@npm:6.0.9": version: 6.0.9 resolution: "react-error-overlay@npm:6.0.9" @@ -22382,6 +22448,20 @@ __metadata: languageName: node linkType: hard +"tiny-invariant@npm:^1.0.6": + version: 1.2.0 + resolution: "tiny-invariant@npm:1.2.0" + checksum: e09a718a7c4a499ba592cdac61f015d87427a0867ca07f50c11fd9b623f90cdba18937b515d4a5e4f43dac92370498d7bdaee0d0e7a377a61095e02c4a92eade + languageName: node + linkType: hard + +"tiny-warning@npm:^1.0.3": + version: 1.0.3 + resolution: "tiny-warning@npm:1.0.3" + checksum: da62c4acac565902f0624b123eed6dd3509bc9a8d30c06e017104bedcf5d35810da8ff72864400ad19c5c7806fc0a8323c68baf3e326af7cb7d969f846100d71 + languageName: node + linkType: hard + "title-case@npm:^3.0.3": version: 3.0.3 resolution: "title-case@npm:3.0.3" From b3b1405586e42d9d44896b043092d2b4a3273e48 Mon Sep 17 00:00:00 2001 From: alcercu Date: Mon, 19 Sep 2022 11:50:20 +0200 Subject: [PATCH 2/6] feat(web): add web3 connect button and wrong chain error boundary --- web/src/app.tsx | 32 ++++++++------ web/src/components/ConnectButton.tsx | 48 ++++++++++++++++++++- web/src/components/WrongChainBoundary.tsx | 44 +++++++++++++++++++ web/src/styles/themes.ts | 2 + web/src/utils/shortenAddress.ts | 14 +++++++ web/src/utils/switchChain.ts | 51 +++++++++++++++++++++++ 6 files changed, 177 insertions(+), 14 deletions(-) create mode 100644 web/src/components/WrongChainBoundary.tsx create mode 100644 web/src/utils/shortenAddress.ts create mode 100644 web/src/utils/switchChain.ts diff --git a/web/src/app.tsx b/web/src/app.tsx index 574781e8a..e03e75d9e 100644 --- a/web/src/app.tsx +++ b/web/src/app.tsx @@ -1,8 +1,10 @@ import React from "react"; -import StyledComponentsProvider from "context/StyledComponentsProvider"; import { SWRConfig } from "swr"; import { request } from "graphql-request"; import { Routes, Route } from "react-router-dom"; +import Web3Provider from "context/Web3Provider"; +import StyledComponentsProvider from "context/StyledComponentsProvider"; +import WrongChainBoundary from "components/WrongChainBoundary"; import Layout from "layout/index"; import Home from "./pages/Home"; import Cases from "./pages/Cases"; @@ -25,18 +27,22 @@ const App: React.FC = () => { ), }} > - - }> - } /> - } /> - Courts} /> - } /> - Justice not found here ¯\_( ͡° ͜ʖ ͡°)_/¯} - /> - - + + + + }> + } /> + } /> + Courts} /> + } /> + Justice not found here ¯\_( ͡° ͜ʖ ͡°)_/¯} + /> + + + + ); diff --git a/web/src/components/ConnectButton.tsx b/web/src/components/ConnectButton.tsx index cae2ffb7a..8f539c856 100644 --- a/web/src/components/ConnectButton.tsx +++ b/web/src/components/ConnectButton.tsx @@ -1,6 +1,52 @@ import React from "react"; +import styled from "styled-components"; +import { shortenAddress } from "utils/shortenAddress"; import { Button } from "@kleros/ui-components-library"; +import { useWeb3 } from "hooks/useWeb3"; +import { useConnect } from "hooks/useConnect"; +import { SUPPORTED_CHAINS } from "consts/supportedChains"; -const ConnectButton: React.FC = () =>