├── src
├── vite-env.d.ts
├── config
│ ├── index.ts
│ ├── connectors
│ │ ├── index.ts
│ │ └── NetworkConnector.ts
│ └── constants
│ │ ├── rpc.ts
│ │ ├── chainId.ts
│ │ └── wallets.ts
├── main.tsx
├── utils
│ ├── isAddress.ts
│ └── index.ts
├── index.css
├── hooks
│ ├── useActiveWeb3React.ts
│ ├── useContract.ts
│ ├── useInactiveListener.ts
│ └── useEagerConnect.ts
├── page
│ └── Home.tsx
├── App.tsx
├── favicon.svg
├── components
│ └── Web3ReactManager
│ │ └── index.tsx
└── logo.svg
├── tsconfig.node.json
├── README.md
├── vite.config.ts
├── .gitignore
├── index.html
├── tsconfig.json
└── package.json
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/config/index.ts:
--------------------------------------------------------------------------------
1 | export const NetworkContextName = "NETWORK";
2 |
--------------------------------------------------------------------------------
/src/config/connectors/index.ts:
--------------------------------------------------------------------------------
1 | export const connectorLocalStorageKey = "connectorId";
2 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "esnext",
5 | "moduleResolution": "node"
6 | },
7 | "include": ["vite.config.ts"]
8 | }
9 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import App from "./App";
4 | import "./index.css";
5 |
6 | ReactDOM.render(, document.getElementById("root"));
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | 1.主要技术栈是 React17+Vite+ethers+web3-react
4 |
5 | 2.config文件夹下是配置文件 主要配置的是连接钱包的参数与常量
6 |
7 | 3.hooks里主要是合约调用的逻辑
8 |
9 | 4.components里的Web3ReactManager是钱包连接逻辑
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/utils/isAddress.ts:
--------------------------------------------------------------------------------
1 | import { getAddress } from "@ethersproject/address";
2 |
3 | export function isAddress(value: any): string | false {
4 | try {
5 | return getAddress(value);
6 | } catch {
7 | return false;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 | import * as path from "path";
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | resolve: {
8 | alias: {
9 | "@": path.join(__dirname, "src"),
10 | },
11 | },
12 | });
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite App
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | import { JsonRpcSigner, Web3Provider } from "@ethersproject/providers";
2 |
3 | // account is not optional
4 | export function getSigner(library: Web3Provider, account: string): JsonRpcSigner {
5 | return library.getSigner(account).connectUnchecked();
6 | }
7 |
8 | // account is optional
9 | export function getProviderOrSigner(library: Web3Provider, account?: string): Web3Provider | JsonRpcSigner {
10 | return account ? getSigner(library, account) : library;
11 | }
12 |
--------------------------------------------------------------------------------
/src/hooks/useActiveWeb3React.ts:
--------------------------------------------------------------------------------
1 | import { Web3Provider } from "@ethersproject/providers";
2 | import { useWeb3React } from "@web3-react/core";
3 | import { Web3ReactContextInterface } from "@web3-react/core/dist/types";
4 | import { NetworkContextName } from "@/config/index";
5 |
6 | export function useActiveWeb3React(): Web3ReactContextInterface {
7 | const context = useWeb3React();
8 | const contextNetwork = useWeb3React(NetworkContextName);
9 |
10 | return context.active ? { ...context } : { ...contextNetwork };
11 | }
12 |
--------------------------------------------------------------------------------
/src/page/Home.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useEffect } from "react";
3 | import { injected } from "@/config/constants/wallets";
4 | import { useActiveWeb3React } from "@/hooks/useActiveWeb3React";
5 | import { connectorLocalStorageKey } from "@/config/connectors/index";
6 |
7 | export default function Home() {
8 | const { account, chainId, error, activate } = useActiveWeb3React();
9 |
10 | useEffect(() => {
11 | console.log(window.localStorage.getItem(connectorLocalStorageKey));
12 |
13 | activate(injected, undefined, true).catch(() => {
14 | activate(injected);
15 | });
16 | }, []);
17 | return {account}
;
18 | }
19 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx",
18 | "baseUrl": "./",
19 | "paths": {
20 | "@/*": ["src/*"]
21 | }
22 | },
23 | "include": ["src"],
24 | "references": [{ "path": "./tsconfig.node.json" }]
25 | }
26 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { HashRouter as Router, Switch, Route } from "react-router-dom";
2 | import { Web3Provider } from "@ethersproject/providers";
3 | import { Web3ReactProvider, createWeb3ReactRoot } from "@web3-react/core";
4 | import Web3ReactManager from "@/components/Web3ReactManager/index";
5 |
6 | import { NetworkContextName } from "@/config";
7 |
8 | import Home from "@/page/Home";
9 |
10 | export function getLibrary(provider: any): Web3Provider {
11 | const library = new Web3Provider(provider);
12 | library.pollingInterval = 15000;
13 | return library;
14 | }
15 | const Web3ProviderNetwork = createWeb3ReactRoot(NetworkContextName);
16 |
17 | function App() {
18 | return (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | }
30 |
31 | export default App;
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-react-app",
3 | "private": true,
4 | "version": "0.0.0",
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "tsc && vite build",
8 | "preview": "vite preview"
9 | },
10 | "dependencies": {
11 | "@ethersproject/address": "^5.6.1",
12 | "@ethersproject/constants": "^5.6.1",
13 | "@ethersproject/contracts": "^5.6.2",
14 | "@ethersproject/providers": "^5.6.8",
15 | "@web3-react/abstract-connector": "^6.0.7",
16 | "@web3-react/core": "^6.1.9",
17 | "@web3-react/injected-connector": "^6.0.7",
18 | "ethers": "^5.6.9",
19 | "events": "^3.3.0",
20 | "react": "^17.0.2",
21 | "react-dom": "^17.0.2",
22 | "react-router-dom": "5",
23 | "web3modal": "^1.9.8"
24 | },
25 | "devDependencies": {
26 | "@types/node": "^18.0.1",
27 | "@types/react": "^17.0.20",
28 | "@types/react-dom": "^17.0.9",
29 | "@types/react-router-dom": "^5.3.3",
30 | "@vitejs/plugin-react": "^1.3.0",
31 | "typescript": "^4.6.3",
32 | "vite": "^2.9.9"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/favicon.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/src/hooks/useContract.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from "react";
2 | import { useActiveWeb3React } from "@/hooks/useActiveWeb3React";
3 | import { JsonRpcSigner, Web3Provider } from "@ethersproject/providers";
4 | import { AddressZero } from "@ethersproject/constants";
5 | import { isAddress } from "@/utils/isAddress";
6 | import { getProviderOrSigner } from "@/utils";
7 | import { Contract } from "@ethersproject/contracts";
8 |
9 | // export const useExampleContract = (address: string, withSignerIfPossible = true) => {
10 | // return useContract(address, ContractAbi, withSignerIfPossible);
11 | // };
12 |
13 | // Multiple chains
14 |
15 | // export const useBatchTransfer = (withSignerIfPossible?: boolean) => {
16 | // const { chainId } = useActiveWeb3React();
17 | // return useContract(getContractAddress(chainId), ContractAbi, withSignerIfPossible);
18 | // };
19 |
20 | export function useContract(address: string | undefined, ABI: any, withSignerIfPossible = true): Contract | null {
21 | const { library, account } = useActiveWeb3React();
22 | return useMemo(() => {
23 | if (!address || address === AddressZero || !ABI || !library) return null;
24 | try {
25 | return getContract(address, ABI, library, withSignerIfPossible && account ? account : undefined);
26 | } catch (error) {
27 | console.error("Failed to get contract", error);
28 | return null;
29 | }
30 | }, [address, ABI, library, withSignerIfPossible, account]);
31 | }
32 |
33 | export function getContract(address: string, ABI: any, library: Web3Provider, account?: string): Contract {
34 | if (!isAddress(address) || address === AddressZero) {
35 | throw Error(`Invalid 'address' parameter '${address}'.`);
36 | }
37 | return new Contract(address, ABI, getProviderOrSigner(library, account));
38 | }
39 |
--------------------------------------------------------------------------------
/src/hooks/useInactiveListener.ts:
--------------------------------------------------------------------------------
1 | import { useWeb3React as useWeb3ReactCore } from "@web3-react/core";
2 | import { useEffect } from "react";
3 |
4 | import { injected } from "@/config/constants/wallets";
5 |
6 | /**
7 | * Use for network and injected - logs user in
8 | * and out after checking what network theyre on
9 | */
10 | function useInactiveListener(suppress = false) {
11 | const { active, error, activate } = useWeb3ReactCore(); // specifically using useWeb3React because of what this hook does
12 |
13 | useEffect(() => {
14 | const { ethereum } = window;
15 |
16 | if (ethereum && ethereum.on && !active && !error && !suppress) {
17 | const handleChainChanged = () => {
18 | // eat errors
19 | activate(injected, undefined, true).catch((error) => {
20 | console.error("Failed to activate after chain changed", error);
21 | });
22 | };
23 |
24 | const handleAccountsChanged = (accounts: string[]) => {
25 | if (accounts.length > 0) {
26 | // eat errors
27 | activate(injected, undefined, true).catch((error) => {
28 | console.error("Failed to activate after accounts changed", error);
29 | });
30 | }
31 | };
32 |
33 | ethereum.on("chainChanged", handleChainChanged);
34 | ethereum.on("accountsChanged", handleAccountsChanged);
35 |
36 | return () => {
37 | if (ethereum.removeListener) {
38 | ethereum.removeListener("chainChanged", handleChainChanged);
39 | ethereum.removeListener("accountsChanged", handleAccountsChanged);
40 | }
41 | };
42 | }
43 | return undefined;
44 | }, [active, error, suppress, activate]);
45 | }
46 |
47 | export default useInactiveListener;
48 |
--------------------------------------------------------------------------------
/src/hooks/useEagerConnect.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { useWeb3React as useWeb3ReactCore } from "@web3-react/core";
3 | import { injected } from "@/config/constants/wallets";
4 | import { isMobile } from "web3modal";
5 | import { connectorLocalStorageKey } from "@/config/connectors/index";
6 | export function useEagerConnect() {
7 | const { activate, active } = useWeb3ReactCore(); // specifically using useWeb3ReactCore because of what this hook does
8 | const [tried, setTried] = useState(false);
9 |
10 | useEffect(() => {
11 | injected.isAuthorized().then((isAuthorized: any) => {
12 | const hasSignedIn = window.localStorage.getItem(connectorLocalStorageKey);
13 | if (isAuthorized && hasSignedIn) {
14 | activate(injected, undefined, true)
15 | // .then(() => window.ethereum.removeAllListeners(['networkChanged']))
16 | .catch(() => {
17 | setTried(true);
18 | });
19 | // @ts-ignore TYPE NEEDS FIXING
20 | window.ethereum.removeAllListeners(["networkChanged"]);
21 | } else {
22 | if (isMobile() && window.ethereum && hasSignedIn) {
23 | activate(injected, undefined, true)
24 | // .then(() => window.ethereum.removeAllListeners(['networkChanged']))
25 | .catch(() => {
26 | setTried(true);
27 | });
28 | // @ts-ignore TYPE NEEDS FIXING
29 | window.ethereum.removeAllListeners(["networkChanged"]);
30 | } else {
31 | setTried(true);
32 | }
33 | }
34 | });
35 | }, [activate]);
36 |
37 | useEffect(() => {
38 | if (active) {
39 | setTried(true);
40 | }
41 | }, [active]);
42 |
43 | return tried;
44 | }
45 |
46 | export default useEagerConnect;
47 |
--------------------------------------------------------------------------------
/src/components/Web3ReactManager/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { useWeb3React } from "@web3-react/core";
3 | import { network } from "@/config/constants/wallets";
4 | import { NetworkContextName } from "@/config/index";
5 |
6 | import useEagerConnect from "@/hooks/useEagerConnect";
7 | import useInactiveListener from "@/hooks/useInactiveListener";
8 |
9 | export default function Web3ReactManager({ children }: { children: JSX.Element }) {
10 | const { active } = useWeb3React();
11 | const { active: networkActive, error: networkError, activate: activateNetwork } = useWeb3React(NetworkContextName);
12 |
13 | // try to eagerly connect to an injected provider, if it exists and has granted access already
14 | const triedEager = useEagerConnect();
15 |
16 | // after eagerly trying injected, if the network connect ever isn't active or in an error state, activate itd
17 | useEffect(() => {
18 | if (triedEager && !networkActive && !networkError && !active) {
19 | activateNetwork(network);
20 | }
21 | }, [triedEager, networkActive, networkError, activateNetwork, active]);
22 |
23 | // when there's no account connected, react to logins (broadly speaking) on the injected provider, if it exists
24 | useInactiveListener(!triedEager);
25 |
26 | // handle delayed loader state
27 | const [showLoader, setShowLoader] = useState(false);
28 | useEffect(() => {
29 | const timeout = setTimeout(() => {
30 | setShowLoader(true);
31 | }, 600);
32 |
33 | return () => {
34 | clearTimeout(timeout);
35 | };
36 | }, []);
37 |
38 | // on page load, do nothing until we've tried to connect to the injected connector
39 | if (!triedEager) {
40 | return null;
41 | }
42 |
43 | // if the account context isn't active, and there's an error on the network context, it's an irrecoverable error
44 | if (!active && networkError) {
45 | return unknownError
;
46 | }
47 |
48 | // if neither context is active, spin
49 | if (!active && !networkActive) {
50 | return showLoader ? Loader
: null;
51 | }
52 |
53 | return children;
54 | }
55 |
--------------------------------------------------------------------------------
/src/config/constants/rpc.ts:
--------------------------------------------------------------------------------
1 | import { ChainId } from "./chainId";
2 |
3 | const RPC = {
4 | [ChainId.ETHEREUM]: "https://api.sushirelay.com/v1",
5 | // [ChainId.ETHEREUM]: 'https://eth-mainnet.alchemyapi.io/v2/HNQXSfiUcPjfpDBQaWYXjqlhTr1cEY9c',
6 | // [ChainId.MAINNET]: 'https://eth-mainnet.alchemyapi.io/v2/q1gSNoSMEzJms47Qn93f9-9Xg5clkmEC',
7 | // [ChainId.ROPSTEN]: "https://eth-ropsten.alchemyapi.io/v2/cidKix2Xr-snU3f6f6Zjq_rYdalKKHmW",
8 | [ChainId.RINKEBY]: "https://eth-rinkeby.alchemyapi.io/v2/XVLwDlhGP6ApBXFz_lfv0aZ6VmurWhYD",
9 | // [ChainId.GÖRLI]: "https://eth-goerli.alchemyapi.io/v2/Dkk5d02QjttYEoGmhZnJG37rKt8Yl3Im",
10 | // [ChainId.KOVAN]: "https://eth-kovan.alchemyapi.io/v2/wnW2uNdwqMPes-BCf9lTWb9UHL9QP2dp",
11 | // [ChainId.FANTOM]: "https://rpcapi.fantom.network",
12 | // [ChainId.FANTOM_TESTNET]: "https://rpc.testnet.fantom.network",
13 | // [ChainId.MATIC]: "https://polygon-rpc.com/",
14 | // [ChainId.MATIC_TESTNET]: "https://rpc-mumbai.matic.today",
15 | // [ChainId.XDAI]: "https://rpc.xdaichain.com",
16 | // [ChainId.BSC]: "https://bsc-dataseed.binance.org/",
17 | // [ChainId.BSC_TESTNET]: "https://data-seed-prebsc-2-s3.binance.org:8545",
18 | // [ChainId.MOONBEAM_TESTNET]: "https://rpc.testnet.moonbeam.network",
19 | // [ChainId.AVALANCHE]: "https://api.avax.network/ext/bc/C/rpc",
20 | // [ChainId.AVALANCHE_TESTNET]: "https://api.avax-test.network/ext/bc/C/rpc",
21 | // [ChainId.HECO]: "https://http-mainnet.hecochain.com",
22 | // [ChainId.HECO_TESTNET]: "https://http-testnet.hecochain.com",
23 | // [ChainId.HARMONY]: "https://api.harmony.one",
24 | // [ChainId.HARMONY_TESTNET]: "https://api.s0.b.hmny.io",
25 | // [ChainId.OKEX]: "https://exchainrpc.okex.org",
26 | // [ChainId.OKEX_TESTNET]: "https://exchaintestrpc.okex.org",
27 | // [ChainId.ARBITRUM]: "https://arb1.arbitrum.io/rpc",
28 | // [ChainId.PALM]: "https://palm-mainnet.infura.io/v3/da5fbfafcca14b109e2665290681e267",
29 | // [ChainId.FUSE]: "https://rpc.fuse.io",
30 | // [ChainId.CELO]: "https://forno.celo.org",
31 | // [ChainId.MOONRIVER]: "https://rpc.moonriver.moonbeam.network",
32 | // [ChainId.TELOS]: "https://mainnet.telos.net/evm",
33 | // [ChainId.MOONBEAM]: "https://rpc.api.moonbeam.network",
34 | };
35 |
36 | export default RPC;
37 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/config/constants/chainId.ts:
--------------------------------------------------------------------------------
1 | // export const ChainId = {
2 | // 1: "ETHEREUM",
3 | // 3: "ROPSTEN",
4 | // 4: "RINKEBY",
5 | // 5: "GÖRLI",
6 | // 40: "TELOS",
7 | // 42: "KOVAN",
8 | // 56: "BSC",
9 | // 65: "OKEX_TESTNET",
10 | // 66: "OKEX",
11 | // 97: "BSC_TESTNET",
12 | // 100: "XDAI",
13 | // 122: "FUSE",
14 | // 128: "HECO",
15 | // 137: "MATIC",
16 | // 250: "FANTOM",
17 | // 256: "HECO_TESTNET",
18 | // 1284: "MOONBEAM",
19 | // 1285: "MOONRIVER",
20 | // 1287: "MOONBEAM_TESTNET",
21 | // 4002: "FANTOM_TESTNET",
22 | // 31337: "HARDHAT",
23 | // 42161: "ARBITRUM",
24 | // 42220: "CELO",
25 | // 43113: "AVALANCHE_TESTNET",
26 | // 43114: "AVALANCHE",
27 | // 80001: "MATIC_TESTNET",
28 | // 1666600000: "HARMONY",
29 | // 1666700000: "HARMONY_TESTNET",
30 | // 11297108099: "PALM_TESTNET",
31 | // 11297108109: "PALM",
32 | // 79377087078960: "ARBITRUM_TESTNET",
33 | // ARBITRUM: 42161,
34 | // ARBITRUM_TESTNET: 79377087078960,
35 | // AVALANCHE: 43114,
36 | // AVALANCHE_TESTNET: 43113,
37 | // BSC: 56,
38 | // BSC_TESTNET: 97,
39 | // CELO: 42220,
40 | // ETHEREUM: 1,
41 | // FANTOM: 250,
42 | // FANTOM_TESTNET: 4002,
43 | // FUSE: 122,
44 | // GÖRLI: 5,
45 | // HARDHAT: 31337,
46 | // HARMONY: 1666600000,
47 | // HARMONY_TESTNET: 1666700000,
48 | // HECO: 128,
49 | // HECO_TESTNET: 256,
50 | // KOVAN: 42,
51 | // MATIC: 137,
52 | // MATIC_TESTNET: 80001,
53 | // MOONBEAM: 1284,
54 | // MOONBEAM_TESTNET: 1287,
55 | // MOONRIVER: 1285,
56 | // OKEX: 66,
57 | // OKEX_TESTNET: 65,
58 | // PALM: 11297108109,
59 | // PALM_TESTNET: 11297108099,
60 | // RINKEBY: 4,
61 | // ROPSTEN: 3,
62 | // TELOS: 40,
63 | // XDAI: 100,
64 | // };
65 |
66 | export enum ChainId {
67 | ETHEREUM = 1,
68 | // ROPSTEN = 3,
69 | RINKEBY = 4,
70 | // GÖRLI = 5,
71 | // KOVAN = 42,
72 | // MATIC = 137,
73 | // MATIC_TESTNET = 80001,
74 | // FANTOM = 250,
75 | // FANTOM_TESTNET = 4002,
76 | // XDAI = 100,
77 | // BSC = 56,
78 | // BSC_TESTNET = 97,
79 | // ARBITRUM = 42161,
80 | // ARBITRUM_TESTNET = 79377087078960,
81 | // MOONBEAM_TESTNET = 1287,
82 | // AVALANCHE = 43114,
83 | // AVALANCHE_TESTNET = 43113,
84 | // HECO = 128,
85 | // HECO_TESTNET = 256,
86 | // HARMONY = 1666600000,
87 | // HARMONY_TESTNET = 1666700000,
88 | // OKEX = 66,
89 | // OKEX_TESTNET = 65,
90 | // CELO = 42220,
91 | // PALM = 11297108109,
92 | // PALM_TESTNET = 11297108099,
93 | // MOONRIVER = 1285,
94 | // FUSE = 122,
95 | // TELOS = 40,
96 | // HARDHAT = 31337,
97 | // MOONBEAM = 1284,
98 | }
99 |
--------------------------------------------------------------------------------
/src/config/connectors/NetworkConnector.ts:
--------------------------------------------------------------------------------
1 | import { ConnectorUpdate } from "@web3-react/types";
2 | import { AbstractConnector } from "@web3-react/abstract-connector";
3 | import invariant from "tiny-invariant";
4 |
5 | interface NetworkConnectorArguments {
6 | urls: { [chainId: number]: string };
7 | defaultChainId?: number;
8 | }
9 |
10 | // taken from ethers.js, compatible interface with web3 provider
11 | type AsyncSendable = {
12 | isMetaMask?: boolean;
13 | host?: string;
14 | path?: string;
15 | sendAsync?: (request: any, callback: (error: any, response: any) => void) => void;
16 | send?: (request: any, callback: (error: any, response: any) => void) => void;
17 | };
18 |
19 | class RequestError extends Error {
20 | constructor(message: string, public code: number, public data?: unknown) {
21 | super(message);
22 | }
23 | }
24 |
25 | interface BatchItem {
26 | request: { jsonrpc: "2.0"; id: number; method: string; params: unknown };
27 | resolve: (result: any) => void;
28 | reject: (error: Error) => void;
29 | }
30 |
31 | class MiniRpcProvider implements AsyncSendable {
32 | public readonly isMetaMask: false = false;
33 |
34 | public readonly chainId: number;
35 |
36 | public readonly url: string;
37 |
38 | public readonly host: string;
39 |
40 | public readonly path: string;
41 |
42 | public readonly batchWaitTimeMs: number;
43 |
44 | private nextId = 1;
45 |
46 | private batchTimeoutId: ReturnType | null = null;
47 |
48 | private batch: BatchItem[] = [];
49 |
50 | constructor(chainId: number, url: string, batchWaitTimeMs?: number) {
51 | this.chainId = chainId;
52 | this.url = url;
53 | const parsed = new URL(url);
54 | this.host = parsed.host;
55 | this.path = parsed.pathname; // how long to wait to batch calls
56 | this.batchWaitTimeMs = batchWaitTimeMs ?? 50;
57 | }
58 |
59 | public readonly clearBatch = async () => {
60 | // console.info('Clearing batch', this.batch)
61 | const { batch } = this;
62 | this.batch = [];
63 | this.batchTimeoutId = null;
64 | let response: Response;
65 | try {
66 | response = await fetch(this.url, {
67 | method: "POST",
68 | headers: { "content-type": "application/json", accept: "application/json" },
69 | body: JSON.stringify(batch.map((item) => item.request)),
70 | });
71 | } catch (error) {
72 | batch.forEach(({ reject }) => reject(new Error("Failed to send batch call")));
73 | return;
74 | }
75 |
76 | if (!response.ok) {
77 | batch.forEach(({ reject }) =>
78 | reject(new RequestError(`${response.status}: ${response.statusText}`, -32000)),
79 | );
80 | return;
81 | }
82 |
83 | let json;
84 | try {
85 | json = await response.json();
86 | } catch (error) {
87 | batch.forEach(({ reject }) => reject(new Error("Failed to parse JSON response")));
88 | return;
89 | }
90 | const byKey = batch.reduce<{ [id: number]: BatchItem }>((memo, current) => {
91 | memo[current.request.id] = current;
92 | return memo;
93 | }, {}); // eslint-disable-next-line no-restricted-syntax
94 | for (const result of json) {
95 | const {
96 | resolve,
97 | reject,
98 | request: { method },
99 | } = byKey[result.id];
100 | if (resolve) {
101 | if ("error" in result) {
102 | reject(new RequestError(result?.error?.message, result?.error?.code, result?.error?.data));
103 | } else if ("result" in result) {
104 | resolve(result.result);
105 | } else {
106 | reject(
107 | new RequestError(`Received unexpected JSON-RPC response to ${method} request.`, -32000, result),
108 | );
109 | }
110 | }
111 | }
112 | };
113 |
114 | public readonly sendAsync = (
115 | request: { jsonrpc: "2.0"; id: number | string | null; method: string; params?: any },
116 | callback: (error: any, response: any) => void,
117 | ): void => {
118 | this.request(request.method, request.params)
119 | .then((result) => callback(null, { jsonrpc: "2.0", id: request.id, result }))
120 | .catch((error) => callback(error, null));
121 | };
122 |
123 | public readonly request = async (
124 | method: string | { method: string; params: unknown[] },
125 | params?: any,
126 | ): Promise => {
127 | if (typeof method !== "string") {
128 | return this.request(method.method, method.params);
129 | }
130 | if (method === "eth_chainId") {
131 | return `0x${this.chainId.toString(16)}`;
132 | }
133 | const promise = new Promise((resolve, reject) => {
134 | this.batch.push({
135 | request: {
136 | jsonrpc: "2.0",
137 | id: this.nextId++,
138 | method,
139 | params,
140 | },
141 | resolve,
142 | reject,
143 | });
144 | });
145 | this.batchTimeoutId = this.batchTimeoutId ?? setTimeout(this.clearBatch, this.batchWaitTimeMs);
146 | return promise;
147 | };
148 | }
149 |
150 | export class NetworkConnector extends AbstractConnector {
151 | private readonly providers: { [chainId: number]: MiniRpcProvider };
152 |
153 | private currentChainId: number;
154 |
155 | constructor({ urls, defaultChainId }: NetworkConnectorArguments) {
156 | invariant(
157 | defaultChainId || Object.keys(urls).length === 1,
158 | "defaultChainId is a required argument with >1 url",
159 | );
160 | super({ supportedChainIds: Object.keys(urls).map((k): number => Number(k)) });
161 |
162 | this.currentChainId = defaultChainId || Number(Object.keys(urls)[0]);
163 | this.providers = Object.keys(urls).reduce<{ [chainId: number]: MiniRpcProvider }>((accumulator, chainId) => {
164 | accumulator[Number(chainId)] = new MiniRpcProvider(Number(chainId), urls[Number(chainId)]);
165 | return accumulator;
166 | }, {});
167 | }
168 |
169 | public get provider(): MiniRpcProvider {
170 | return this.providers[this.currentChainId];
171 | }
172 |
173 | public async activate(): Promise {
174 | return { provider: this.providers[this.currentChainId], chainId: this.currentChainId, account: null };
175 | }
176 |
177 | public async getProvider(): Promise {
178 | return this.providers[this.currentChainId];
179 | }
180 |
181 | public async getChainId(): Promise {
182 | return this.currentChainId;
183 | }
184 |
185 | public async getAccount(): Promise {
186 | return null;
187 | }
188 |
189 | public deactivate() {
190 | return null;
191 | }
192 | }
193 |
194 | export default NetworkConnector;
195 |
--------------------------------------------------------------------------------
/src/config/constants/wallets.ts:
--------------------------------------------------------------------------------
1 | import { ChainId } from "./chainId";
2 | import { AbstractConnector } from "@web3-react/abstract-connector";
3 | import { InjectedConnector } from "@web3-react/injected-connector";
4 | import { NetworkConnector } from "@/config/connectors/NetworkConnector";
5 | import RPC from "./rpc";
6 |
7 | const supportedChainIds = Object.values(ChainId) as number[];
8 |
9 | export const network = new NetworkConnector({
10 | defaultChainId: 4,
11 | urls: RPC,
12 | });
13 |
14 | export const injected = new InjectedConnector({
15 | supportedChainIds,
16 | });
17 |
18 | export interface WalletInfo {
19 | connector?: (() => Promise) | AbstractConnector;
20 | name: string;
21 | iconName: string;
22 | description: string;
23 | href: string | null;
24 | color: string;
25 | primary?: true;
26 | mobile?: true;
27 | mobileOnly?: true;
28 | }
29 |
30 | export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
31 | INJECTED: {
32 | connector: injected,
33 | name: "Injected",
34 | iconName: "injected.svg",
35 | description: "Injected web3 provider.",
36 | href: null,
37 | color: "#010101",
38 | primary: true,
39 | },
40 | METAMASK: {
41 | connector: injected,
42 | name: "MetaMask",
43 | iconName: "metamask.png",
44 | description: "Easy-to-use browser extension.",
45 | href: null,
46 | color: "#E8831D",
47 | },
48 | METAMASK_MOBILE: {
49 | name: "MetaMask",
50 | iconName: "metamask.png",
51 | description: "Open in MetaMask app.",
52 | href: "https://metamask.app.link/dapp/app.sushi.com",
53 | color: "#E8831D",
54 | mobile: true,
55 | mobileOnly: true,
56 | },
57 | // WALLET_CONNECT: {
58 | // connector: async () => {
59 | // const WalletConnectConnector = (await import("@web3-react/walletconnect-connector")).WalletConnectConnector;
60 | // return new WalletConnectConnector({
61 | // rpc: RPC,
62 | // bridge: "https://bridge.walletconnect.org",
63 | // qrcode: true,
64 | // supportedChainIds,
65 | // });
66 | // },
67 | // name: "WalletConnect",
68 | // iconName: "wallet-connect.svg",
69 | // description: "Connect to Trust Wallet, Rainbow Wallet and more...",
70 | // href: null,
71 | // color: "#4196FC",
72 | // mobile: true,
73 | // },
74 | // KEYSTONE: {
75 | // connector: async () => {
76 | // const KeystoneConnector = (await import("@keystonehq/keystone-connector")).KeystoneConnector;
77 | // return new KeystoneConnector({
78 | // chainId: 1,
79 | // url: RPC[ChainId.ETHEREUM],
80 | // });
81 | // },
82 | // name: "Keystone",
83 | // iconName: "keystone.png",
84 | // description: "Connect to Keystone hardware wallet.",
85 | // href: null,
86 | // color: "#4196FC",
87 | // mobile: true,
88 | // },
89 | // LATTICE: {
90 | // connector: async () => {
91 | // const LatticeConnector = (await import("@web3-react/lattice-connector")).LatticeConnector;
92 | // return new LatticeConnector({
93 | // chainId: 1,
94 | // url: RPC[ChainId.ETHEREUM],
95 | // appName: "SushiSwap",
96 | // });
97 | // },
98 | // name: "Lattice",
99 | // iconName: "lattice.png",
100 | // description: "Connect to GridPlus Wallet.",
101 | // href: null,
102 | // color: "#40a9ff",
103 | // mobile: true,
104 | // },
105 | // WALLET_LINK: {
106 | // connector: async () => {
107 | // const WalletLinkConnector = (await import("@web3-react/walletlink-connector")).WalletLinkConnector;
108 | // return new WalletLinkConnector({
109 | // url: RPC[ChainId.ETHEREUM],
110 | // appName: "SushiSwap",
111 | // appLogoUrl: "https://raw.githubusercontent.com/sushiswap/art/master/sushi/logo-256x256.png",
112 | // darkMode: true,
113 | // });
114 | // },
115 | // name: "Coinbase Wallet",
116 | // iconName: "coinbase.svg",
117 | // description: "Use Coinbase Wallet app on mobile device",
118 | // href: null,
119 | // color: "#315CF5",
120 | // },
121 | // COINBASE_LINK: {
122 | // name: "Open in Coinbase Wallet",
123 | // iconName: "coinbase.svg",
124 | // description: "Open in Coinbase Wallet app.",
125 | // href: "https://go.cb-w.com",
126 | // color: "#315CF5",
127 | // mobile: true,
128 | // mobileOnly: true,
129 | // },
130 | // FORTMATIC: {
131 | // connector: async () => {
132 | // const FortmaticConnector = (await import("@web3-react/fortmatic-connector")).FortmaticConnector;
133 | // return new FortmaticConnector({
134 | // apiKey: process.env.NEXT_PUBLIC_FORTMATIC_API_KEY ?? "",
135 | // chainId: 1,
136 | // });
137 | // },
138 | // name: "Fortmatic",
139 | // iconName: "fortmatic.png",
140 | // description: "Login using Fortmatic hosted wallet",
141 | // href: null,
142 | // color: "#6748FF",
143 | // mobile: true,
144 | // },
145 | // Portis: {
146 | // connector: async () => {
147 | // const PortisConnector = (await import("@web3-react/portis-connector")).PortisConnector;
148 | // return new PortisConnector({
149 | // dAppId: process.env.NEXT_PUBLIC_PORTIS_ID ?? "",
150 | // networks: [1],
151 | // });
152 | // },
153 | // name: "Portis",
154 | // iconName: "portis.png",
155 | // description: "Login using Portis hosted wallet",
156 | // href: null,
157 | // color: "#4A6C9B",
158 | // mobile: true,
159 | // },
160 | // Torus: {
161 | // connector: async () => {
162 | // const TorusConnector = (await import("@web3-react/torus-connector")).TorusConnector;
163 | // return new TorusConnector({
164 | // chainId: 1,
165 | // });
166 | // },
167 | // name: "Torus",
168 | // iconName: "torus.png",
169 | // description: "Login using Torus hosted wallet",
170 | // href: null,
171 | // color: "#315CF5",
172 | // mobile: true,
173 | // },
174 | // Binance: {
175 | // connector: async () => {
176 | // const BscConnector = (await import("@binance-chain/bsc-connector")).BscConnector;
177 | // return new BscConnector({
178 | // supportedChainIds: [56],
179 | // });
180 | // },
181 | // name: "Binance",
182 | // iconName: "bsc.jpg",
183 | // description: "Login using Binance hosted wallet",
184 | // href: null,
185 | // color: "#F0B90B",
186 | // mobile: true,
187 | // },
188 | // Clover: {
189 | // connector: async () => {
190 | // const CloverConnector = (await import("@clover-network/clover-connector")).CloverConnector;
191 | // return new CloverConnector({
192 | // supportedChainIds: [1],
193 | // });
194 | // },
195 | // name: "Clover",
196 | // iconName: "clover.svg",
197 | // description: "Login using Clover hosted wallet",
198 | // href: null,
199 | // color: "#269964",
200 | // },
201 | };
202 |
--------------------------------------------------------------------------------