├── .nvmrc ├── packages ├── core │ ├── README.md │ ├── src │ │ ├── index.tsx │ │ ├── provider.tsx │ │ ├── hooks.spec.ts │ │ └── hooks.ts │ ├── tsconfig.json │ └── package.json ├── empty │ ├── README.md │ ├── tsconfig.json │ ├── src │ │ ├── index.spec.ts │ │ └── index.ts │ └── package.json ├── store │ ├── README.md │ ├── tsconfig.json │ ├── package.json │ └── src │ │ ├── index.ts │ │ └── index.spec.ts ├── types │ ├── README.md │ ├── tsconfig.json │ ├── package.json │ └── src │ │ └── index.ts ├── url │ ├── README.md │ ├── tsconfig.json │ ├── package.json │ └── src │ │ ├── index.spec.ts │ │ └── index.ts ├── eip1193 │ ├── README.md │ ├── tsconfig.json │ ├── package.json │ └── src │ │ ├── mock.ts │ │ ├── index.ts │ │ └── index.spec.ts ├── network │ ├── README.md │ ├── tsconfig.json │ ├── package.json │ └── src │ │ ├── utils.ts │ │ ├── utils.spec.ts │ │ ├── index.ts │ │ └── index.spec.ts ├── metamask │ ├── README.md │ ├── tsconfig.json │ ├── package.json │ └── src │ │ ├── index.spec.ts │ │ └── index.ts ├── gnosis-safe │ ├── README.md │ ├── tsconfig.json │ ├── package.json │ └── src │ │ └── index.ts ├── coinbase-wallet │ ├── README.md │ ├── tsconfig.json │ ├── package.json │ └── src │ │ ├── index.spec.ts │ │ └── index.ts ├── walletconnect │ ├── README.md │ ├── tsconfig.json │ ├── package.json │ └── src │ │ ├── utils.spec.ts │ │ ├── index.spec.ts │ │ ├── utils.ts │ │ └── index.ts └── walletconnect-v2 │ ├── README.md │ ├── tsconfig.json │ ├── package.json │ └── src │ ├── utils.spec.ts │ ├── utils.ts │ ├── index.spec.ts │ └── index.ts ├── .eslintignore ├── .gitignore ├── example ├── README.md ├── .env ├── connectors │ ├── empty.ts │ ├── metaMask.ts │ ├── gnosisSafe.ts │ ├── url.ts │ ├── network.ts │ ├── walletConnect.ts │ ├── coinbaseWallet.ts │ ├── walletConnectV2.ts │ └── eip1193.ts ├── next-env.d.ts ├── next.config.js ├── components │ ├── Chain.tsx │ ├── Status.tsx │ ├── connectorCards │ │ ├── MetaMaskCard.tsx │ │ ├── GnosisSafeCard.tsx │ │ ├── CoinbaseWalletCard.tsx │ │ ├── NetworkCard.tsx │ │ ├── WalletConnectCard.tsx │ │ └── WalletConnectV2Card.tsx │ ├── ProviderExample.tsx │ ├── Accounts.tsx │ ├── Card.tsx │ └── ConnectWithSelect.tsx ├── tsconfig.json ├── utils.ts ├── pages │ └── index.tsx ├── package.json └── chains.ts ├── jest.config.js ├── .eslintrc.js ├── lerna.json ├── tsconfig.json ├── .github └── workflows │ └── CI.yml ├── package.json └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | v16 2 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | # @web3-react/core 2 | -------------------------------------------------------------------------------- /packages/empty/README.md: -------------------------------------------------------------------------------- 1 | # @web3-react/empty 2 | -------------------------------------------------------------------------------- /packages/store/README.md: -------------------------------------------------------------------------------- 1 | # @web3-react/store 2 | -------------------------------------------------------------------------------- /packages/types/README.md: -------------------------------------------------------------------------------- 1 | # @web3-react/types 2 | -------------------------------------------------------------------------------- /packages/url/README.md: -------------------------------------------------------------------------------- 1 | # @web3-react/url 2 | -------------------------------------------------------------------------------- /packages/eip1193/README.md: -------------------------------------------------------------------------------- 1 | # @web3-react/eip1193 2 | -------------------------------------------------------------------------------- /packages/network/README.md: -------------------------------------------------------------------------------- 1 | # @web3-react/network 2 | -------------------------------------------------------------------------------- /packages/metamask/README.md: -------------------------------------------------------------------------------- 1 | # @web3-react/metamask 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | *.js 4 | *.spec.ts 5 | -------------------------------------------------------------------------------- /packages/gnosis-safe/README.md: -------------------------------------------------------------------------------- 1 | # @web3-react/gnosis-safe 2 | -------------------------------------------------------------------------------- /packages/coinbase-wallet/README.md: -------------------------------------------------------------------------------- 1 | # @web3-react/coinbase-wallet 2 | -------------------------------------------------------------------------------- /packages/walletconnect/README.md: -------------------------------------------------------------------------------- 1 | # @web3-react/walletconnect 2 | -------------------------------------------------------------------------------- /packages/walletconnect-v2/README.md: -------------------------------------------------------------------------------- 1 | # @web3-react/walletconnect-v2 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | packages/*/dist/ 3 | example/.next/ 4 | .env.local 5 | -------------------------------------------------------------------------------- /packages/core/src/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './hooks' 2 | export * from './provider' 3 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # example 2 | 3 | This is an example app built with [Next.js](https://nextjs.org/) that showcases some basic web3-react usage patterns. 4 | -------------------------------------------------------------------------------- /packages/url/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["./src"], 4 | "compilerOptions": { 5 | "outDir": "./dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /example/.env: -------------------------------------------------------------------------------- 1 | INFURA_KEY=84842078b09946638c03157f83405213 2 | ALCHEMY_KEY=_gg7wSSi0KMBsdKnGVfHDueq6xMB9EkC 3 | WALLET_CONNECT_PROJECT_ID=a6cc11517a10f6f12953fd67b1eb67e7 4 | -------------------------------------------------------------------------------- /packages/eip1193/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["./src"], 4 | "compilerOptions": { 5 | "outDir": "./dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/empty/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["./src"], 4 | "compilerOptions": { 5 | "outDir": "./dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/metamask/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["./src"], 4 | "compilerOptions": { 5 | "outDir": "./dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/network/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["./src"], 4 | "compilerOptions": { 5 | "outDir": "./dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/store/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["./src"], 4 | "compilerOptions": { 5 | "outDir": "./dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["./src"], 4 | "compilerOptions": { 5 | "outDir": "./dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/gnosis-safe/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["./src"], 4 | "compilerOptions": { 5 | "outDir": "./dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/walletconnect/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["./src"], 4 | "compilerOptions": { 5 | "outDir": "./dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/coinbase-wallet/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["./src"], 4 | "compilerOptions": { 5 | "outDir": "./dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/walletconnect-v2/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["./src"], 4 | "compilerOptions": { 5 | "outDir": "./dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["./src"], 4 | "compilerOptions": { 5 | "jsx": "react", 6 | "outDir": "./dist" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'jsdom', 4 | moduleNameMapper: { 5 | '^@web3-react/(.*)$': '/packages/$1/src', 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /example/connectors/empty.ts: -------------------------------------------------------------------------------- 1 | import { initializeConnector } from '@web3-react/core' 2 | import { EMPTY, Empty } from '@web3-react/empty' 3 | 4 | export const [empty, hooks] = initializeConnector(() => EMPTY) 5 | -------------------------------------------------------------------------------- /packages/empty/src/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { EMPTY } from '.' 2 | 3 | describe('EMPTY', () => { 4 | test('#activate', () => { 5 | EMPTY.activate() 6 | expect(EMPTY.provider).toBeUndefined() 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | require('@uniswap/eslint-config/load') 4 | 5 | module.exports = { 6 | extends: '@uniswap/eslint-config/react', 7 | rules: { 8 | 'import/no-unused-modules': 'off' 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /example/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "independent", 3 | "packages": [ 4 | "packages/*", 5 | "example" 6 | ], 7 | "npmClient": "yarn", 8 | "ignoreChanges": ["**/*.md", "**/*.spec.ts"], 9 | "useWorkspaces": true 10 | } 11 | -------------------------------------------------------------------------------- /example/connectors/metaMask.ts: -------------------------------------------------------------------------------- 1 | import { initializeConnector } from '@web3-react/core' 2 | import { MetaMask } from '@web3-react/metamask' 3 | 4 | export const [metaMask, hooks] = initializeConnector((actions) => new MetaMask({ actions })) 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/recommended/tsconfig.json", 3 | "compilerOptions": { 4 | "module": "CommonJS", 5 | "declaration": true, 6 | "moduleResolution": "Node" 7 | }, 8 | "exclude": ["**/*.spec.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /example/connectors/gnosisSafe.ts: -------------------------------------------------------------------------------- 1 | import { initializeConnector } from '@web3-react/core' 2 | import { GnosisSafe } from '@web3-react/gnosis-safe' 3 | 4 | export const [gnosisSafe, hooks] = initializeConnector((actions) => new GnosisSafe({ actions })) 5 | -------------------------------------------------------------------------------- /example/connectors/url.ts: -------------------------------------------------------------------------------- 1 | import { initializeConnector } from '@web3-react/core' 2 | import { Url } from '@web3-react/url' 3 | 4 | import { URLS } from '../chains' 5 | 6 | export const [url, hooks] = initializeConnector((actions) => new Url({ actions, url: URLS[1][0] })) 7 | -------------------------------------------------------------------------------- /example/connectors/network.ts: -------------------------------------------------------------------------------- 1 | import { initializeConnector } from '@web3-react/core' 2 | import { Network } from '@web3-react/network' 3 | 4 | import { URLS } from '../chains' 5 | 6 | export const [network, hooks] = initializeConnector((actions) => new Network({ actions, urlMap: URLS })) 7 | -------------------------------------------------------------------------------- /example/next.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('next').NextConfig} 3 | */ 4 | const nextConfig = { 5 | env: { 6 | infuraKey: process.env.INFURA_KEY, 7 | alchemyKey: process.env.ALCHEMY_KEY, 8 | walletConnectProjectId: process.env.WALLET_CONNECT_PROJECT_ID, 9 | }, 10 | } 11 | 12 | module.exports = nextConfig 13 | -------------------------------------------------------------------------------- /example/connectors/walletConnect.ts: -------------------------------------------------------------------------------- 1 | import { initializeConnector } from '@web3-react/core' 2 | import { WalletConnect } from '@web3-react/walletconnect' 3 | 4 | import { URLS } from '../chains' 5 | 6 | export const [walletConnect, hooks] = initializeConnector( 7 | (actions) => 8 | new WalletConnect({ 9 | actions, 10 | options: { 11 | rpc: URLS, 12 | }, 13 | }) 14 | ) 15 | -------------------------------------------------------------------------------- /example/connectors/coinbaseWallet.ts: -------------------------------------------------------------------------------- 1 | import { CoinbaseWallet } from '@web3-react/coinbase-wallet' 2 | import { initializeConnector } from '@web3-react/core' 3 | 4 | import { URLS } from '../chains' 5 | 6 | export const [coinbaseWallet, hooks] = initializeConnector( 7 | (actions) => 8 | new CoinbaseWallet({ 9 | actions, 10 | options: { 11 | url: URLS[1][0], 12 | appName: 'web3-react', 13 | }, 14 | }) 15 | ) 16 | -------------------------------------------------------------------------------- /packages/empty/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Connector } from '@web3-react/types' 2 | 3 | export class Empty extends Connector { 4 | /** {@inheritdoc Connector.provider} */ 5 | provider: undefined 6 | 7 | /** 8 | * No-op. May be called if it simplifies application code. 9 | */ 10 | public activate() { 11 | void 0 12 | } 13 | } 14 | 15 | // @ts-expect-error actions aren't validated and are only used to set a protected property, so this is ok 16 | export const EMPTY = new Empty() 17 | -------------------------------------------------------------------------------- /example/connectors/walletConnectV2.ts: -------------------------------------------------------------------------------- 1 | import { initializeConnector } from '@web3-react/core' 2 | import { WalletConnect as WalletConnectV2 } from '@web3-react/walletconnect-v2' 3 | 4 | import { MAINNET_CHAINS } from '../chains' 5 | 6 | export const [walletConnectV2, hooks] = initializeConnector( 7 | (actions) => 8 | new WalletConnectV2({ 9 | actions, 10 | options: { 11 | projectId: process.env.walletConnectProjectId, 12 | chains: Object.keys(MAINNET_CHAINS).map(Number), 13 | }, 14 | }) 15 | ) 16 | -------------------------------------------------------------------------------- /example/components/Chain.tsx: -------------------------------------------------------------------------------- 1 | import type { Web3ReactHooks } from '@web3-react/core' 2 | 3 | import { CHAINS } from '../chains' 4 | 5 | export function Chain({ chainId }: { chainId: ReturnType }) { 6 | if (chainId === undefined) return null 7 | 8 | const name = chainId ? CHAINS[chainId]?.name : undefined 9 | 10 | if (name) { 11 | return ( 12 |
13 | Chain:{' '} 14 | 15 | {name} ({chainId}) 16 | 17 |
18 | ) 19 | } 20 | 21 | return ( 22 |
23 | Chain Id: {chainId} 24 |
25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": false, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve", 20 | "incremental": true 21 | }, 22 | "include": [ 23 | "next-env.d.ts", 24 | "**/*.ts", 25 | "**/*.tsx" 26 | ], 27 | "exclude": [ 28 | "node_modules" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /packages/types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@web3-react/types", 3 | "keywords": [ 4 | "web3-react" 5 | ], 6 | "author": "Noah Zinsmeister ", 7 | "license": "GPL-3.0-or-later", 8 | "repository": "github:Uniswap/web3-react", 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "version": "8.1.0-beta.0", 13 | "files": [ 14 | "dist/*" 15 | ], 16 | "type": "commonjs", 17 | "types": "./dist/index.d.ts", 18 | "main": "./dist/index.js", 19 | "exports": "./dist/index.js", 20 | "scripts": { 21 | "prebuild": "rm -rf dist", 22 | "build": "tsc", 23 | "start": "tsc --watch" 24 | }, 25 | "dependencies": { 26 | "zustand": "4.3.3" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example/components/Status.tsx: -------------------------------------------------------------------------------- 1 | import type { Web3ReactHooks } from '@web3-react/core' 2 | 3 | export function Status({ 4 | isActivating, 5 | isActive, 6 | error, 7 | }: { 8 | isActivating: ReturnType 9 | isActive: ReturnType 10 | error?: Error 11 | }) { 12 | return ( 13 |
14 | {error ? ( 15 | <> 16 | 🔴 {error.name ?? 'Error'} 17 | {error.message ? `: ${error.message}` : null} 18 | 19 | ) : isActivating ? ( 20 | <>🟡 Connecting 21 | ) : isActive ? ( 22 | <>🟢 Connected 23 | ) : ( 24 | <>⚪️ Disconnected 25 | )} 26 |
27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /packages/empty/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@web3-react/empty", 3 | "keywords": [ 4 | "web3-react" 5 | ], 6 | "author": "Noah Zinsmeister ", 7 | "license": "GPL-3.0-or-later", 8 | "repository": "github:Uniswap/web3-react", 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "version": "8.1.0-beta.0", 13 | "files": [ 14 | "dist/*" 15 | ], 16 | "type": "commonjs", 17 | "types": "./dist/index.d.ts", 18 | "main": "./dist/index.js", 19 | "exports": "./dist/index.js", 20 | "scripts": { 21 | "prebuild": "rm -rf dist", 22 | "build": "tsc", 23 | "start": "tsc --watch" 24 | }, 25 | "dependencies": { 26 | "@web3-react/types": "^8.1.0-beta.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | test: 13 | runs-on: ${{ matrix.os }} 14 | 15 | strategy: 16 | matrix: 17 | os: 18 | - ubuntu-latest 19 | - macOS-latest 20 | node_version: 21 | - 14 22 | - 16 23 | 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: actions/setup-node@v2 27 | with: 28 | node-version: ${{ matrix.node_version }} 29 | cache: 'yarn' 30 | 31 | - run: yarn 32 | 33 | - run: yarn build 34 | 35 | - run: yarn lint 36 | - run: yarn test 37 | 38 | - run: yarn lerna exec 'yarn next build' --scope=example 39 | -------------------------------------------------------------------------------- /packages/store/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@web3-react/store", 3 | "keywords": [ 4 | "web3-react" 5 | ], 6 | "author": "Noah Zinsmeister ", 7 | "license": "GPL-3.0-or-later", 8 | "repository": "github:Uniswap/web3-react", 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "version": "8.1.0-beta.0", 13 | "files": [ 14 | "dist/*" 15 | ], 16 | "type": "commonjs", 17 | "types": "./dist/index.d.ts", 18 | "main": "./dist/index.js", 19 | "exports": "./dist/index.js", 20 | "scripts": { 21 | "prebuild": "rm -rf dist", 22 | "build": "tsc", 23 | "start": "tsc --watch" 24 | }, 25 | "dependencies": { 26 | "@ethersproject/address": "^5", 27 | "@web3-react/types": "^8.1.0-beta.0", 28 | "zustand": "4.3.3" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/url/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@web3-react/url", 3 | "keywords": [ 4 | "web3-react" 5 | ], 6 | "author": "Noah Zinsmeister ", 7 | "license": "GPL-3.0-or-later", 8 | "repository": "github:Uniswap/web3-react", 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "version": "8.1.0-beta.0", 13 | "files": [ 14 | "dist/*" 15 | ], 16 | "type": "commonjs", 17 | "types": "./dist/index.d.ts", 18 | "main": "./dist/index.js", 19 | "exports": "./dist/index.js", 20 | "scripts": { 21 | "prebuild": "rm -rf dist", 22 | "build": "tsc", 23 | "start": "tsc --watch" 24 | }, 25 | "dependencies": { 26 | "@ethersproject/providers": "^5", 27 | "@web3-react/types": "^8.1.0-beta.0" 28 | }, 29 | "devDependencies": { 30 | "@web3-react/store": "^8.1.0-beta.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/network/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@web3-react/network", 3 | "keywords": [ 4 | "web3-react" 5 | ], 6 | "author": "Noah Zinsmeister ", 7 | "license": "GPL-3.0-or-later", 8 | "repository": "github:Uniswap/web3-react", 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "version": "8.1.0-beta.0", 13 | "files": [ 14 | "dist/*" 15 | ], 16 | "type": "commonjs", 17 | "types": "./dist/index.d.ts", 18 | "main": "./dist/index.js", 19 | "exports": "./dist/index.js", 20 | "scripts": { 21 | "prebuild": "rm -rf dist", 22 | "build": "tsc", 23 | "start": "tsc --watch" 24 | }, 25 | "dependencies": { 26 | "@ethersproject/providers": "^5", 27 | "@web3-react/types": "^8.1.0-beta.0" 28 | }, 29 | "devDependencies": { 30 | "@web3-react/store": "^8.1.0-beta.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/gnosis-safe/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@web3-react/gnosis-safe", 3 | "keywords": [ 4 | "web3-react", 5 | "gnosis-safe" 6 | ], 7 | "author": "Noah Zinsmeister ", 8 | "license": "GPL-3.0-or-later", 9 | "repository": "github:Uniswap/web3-react", 10 | "publishConfig": { 11 | "access": "public" 12 | }, 13 | "version": "8.1.0-beta.0", 14 | "files": [ 15 | "dist/*" 16 | ], 17 | "type": "commonjs", 18 | "types": "./dist/index.d.ts", 19 | "main": "./dist/index.js", 20 | "exports": "./dist/index.js", 21 | "scripts": { 22 | "prebuild": "rm -rf dist", 23 | "build": "tsc", 24 | "start": "tsc --watch" 25 | }, 26 | "dependencies": { 27 | "@safe-global/safe-apps-provider": "^0.16.0", 28 | "@safe-global/safe-apps-sdk": "^7.10.0", 29 | "@web3-react/types": "^8.1.0-beta.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/metamask/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@web3-react/metamask", 3 | "keywords": [ 4 | "web3-react", 5 | "metamask" 6 | ], 7 | "author": "Noah Zinsmeister ", 8 | "license": "GPL-3.0-or-later", 9 | "repository": "github:Uniswap/web3-react", 10 | "publishConfig": { 11 | "access": "public" 12 | }, 13 | "version": "8.1.0-beta.0", 14 | "files": [ 15 | "dist/*" 16 | ], 17 | "type": "commonjs", 18 | "types": "./dist/index.d.ts", 19 | "main": "./dist/index.js", 20 | "exports": "./dist/index.js", 21 | "scripts": { 22 | "prebuild": "rm -rf dist", 23 | "build": "tsc", 24 | "start": "tsc --watch" 25 | }, 26 | "dependencies": { 27 | "@metamask/detect-provider": "^1.2.0", 28 | "@web3-react/types": "^8.1.0-beta.0" 29 | }, 30 | "devDependencies": { 31 | "@web3-react/store": "^8.1.0-beta.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /example/utils.ts: -------------------------------------------------------------------------------- 1 | import { CoinbaseWallet } from '@web3-react/coinbase-wallet' 2 | import { GnosisSafe } from '@web3-react/gnosis-safe' 3 | import { MetaMask } from '@web3-react/metamask' 4 | import { Network } from '@web3-react/network' 5 | import type { Connector } from '@web3-react/types' 6 | import { WalletConnect as WalletConnect } from '@web3-react/walletconnect' 7 | import { WalletConnect as WalletConnectV2 } from '@web3-react/walletconnect-v2' 8 | 9 | export function getName(connector: Connector) { 10 | if (connector instanceof MetaMask) return 'MetaMask' 11 | if (connector instanceof WalletConnectV2) return 'WalletConnect V2' 12 | if (connector instanceof WalletConnect) return 'WalletConnect' 13 | if (connector instanceof CoinbaseWallet) return 'Coinbase Wallet' 14 | if (connector instanceof Network) return 'Network' 15 | if (connector instanceof GnosisSafe) return 'Gnosis Safe' 16 | return 'Unknown' 17 | } 18 | -------------------------------------------------------------------------------- /packages/eip1193/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@web3-react/eip1193", 3 | "keywords": [ 4 | "web3-react", 5 | "eip1193" 6 | ], 7 | "author": "Noah Zinsmeister ", 8 | "license": "GPL-3.0-or-later", 9 | "repository": "github:Uniswap/web3-react", 10 | "publishConfig": { 11 | "access": "public" 12 | }, 13 | "version": "8.1.0-beta.0", 14 | "files": [ 15 | "dist/*" 16 | ], 17 | "type": "commonjs", 18 | "types": "./dist/index.d.ts", 19 | "main": "./dist/index.js", 20 | "exports": "./dist/index.js", 21 | "scripts": { 22 | "prebuild": "rm -rf dist", 23 | "build": "tsc", 24 | "start": "tsc --watch" 25 | }, 26 | "dependencies": { 27 | "@web3-react/types": "^8.1.0-beta.0" 28 | }, 29 | "devDependencies": { 30 | "@ethersproject/experimental": "^5.6.0", 31 | "@ethersproject/providers": "^5.6.0", 32 | "@web3-react/store": "^8.1.0-beta.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/coinbase-wallet/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@web3-react/coinbase-wallet", 3 | "keywords": [ 4 | "web3-react", 5 | "coinbase-wallet" 6 | ], 7 | "author": "Noah Zinsmeister ", 8 | "license": "GPL-3.0-or-later", 9 | "repository": "github:Uniswap/web3-react", 10 | "publishConfig": { 11 | "access": "public" 12 | }, 13 | "version": "8.1.0-beta.0", 14 | "files": [ 15 | "dist/*" 16 | ], 17 | "type": "commonjs", 18 | "types": "./dist/index.d.ts", 19 | "main": "./dist/index.js", 20 | "exports": "./dist/index.js", 21 | "scripts": { 22 | "prebuild": "rm -rf dist", 23 | "build": "tsc", 24 | "start": "tsc --watch" 25 | }, 26 | "dependencies": { 27 | "@web3-react/types": "^8.1.0-beta.0" 28 | }, 29 | "peerDependencies": { 30 | "@coinbase/wallet-sdk": "^3.0.4" 31 | }, 32 | "devDependencies": { 33 | "@web3-react/store": "^8.1.0-beta.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@web3-react/core", 3 | "keywords": [ 4 | "web3-react" 5 | ], 6 | "author": "Noah Zinsmeister ", 7 | "license": "GPL-3.0-or-later", 8 | "repository": "github:Uniswap/web3-react", 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "version": "8.1.0-beta.0", 13 | "files": [ 14 | "dist/*" 15 | ], 16 | "type": "commonjs", 17 | "types": "./dist/index.d.ts", 18 | "main": "./dist/index.js", 19 | "exports": "./dist/index.js", 20 | "scripts": { 21 | "prebuild": "rm -rf dist", 22 | "build": "tsc", 23 | "start": "tsc --watch" 24 | }, 25 | "dependencies": { 26 | "@web3-react/store": "^8.1.0-beta.0", 27 | "@web3-react/types": "^8.1.0-beta.0", 28 | "zustand": "4.3.3" 29 | }, 30 | "peerDependencies": { 31 | "react": ">=16.8" 32 | }, 33 | "optionalDependencies": { 34 | "@ethersproject/providers": "^5" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/walletconnect-v2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@web3-react/walletconnect-v2", 3 | "keywords": [ 4 | "web3-react", 5 | "walletconnect" 6 | ], 7 | "author": "Mike Grabowski ", 8 | "license": "GPL-3.0-or-later", 9 | "repository": "github:Uniswap/web3-react", 10 | "publishConfig": { 11 | "access": "public" 12 | }, 13 | "version": "8.1.0-beta.0", 14 | "files": [ 15 | "dist/*" 16 | ], 17 | "type": "commonjs", 18 | "types": "./dist/index.d.ts", 19 | "main": "./dist/index.js", 20 | "exports": "./dist/index.js", 21 | "scripts": { 22 | "prebuild": "rm -rf dist", 23 | "build": "tsc", 24 | "start": "tsc --watch" 25 | }, 26 | "dependencies": { 27 | "@web3-react/types": "^8.1.0-beta.0", 28 | "eventemitter3": "^4.0.7", 29 | "@walletconnect/ethereum-provider": "^2.4.7" 30 | }, 31 | "devDependencies": { 32 | "@web3-react/store": "^8.1.0-beta.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import CoinbaseWalletCard from '../components/connectorCards/CoinbaseWalletCard' 2 | import GnosisSafeCard from '../components/connectorCards/GnosisSafeCard' 3 | import MetaMaskCard from '../components/connectorCards/MetaMaskCard' 4 | import NetworkCard from '../components/connectorCards/NetworkCard' 5 | import WalletConnectCard from '../components/connectorCards/WalletConnectCard' 6 | import WalletConnectV2Card from '../components/connectorCards/WalletConnectV2Card' 7 | import ProviderExample from '../components/ProviderExample' 8 | 9 | export default function Home() { 10 | return ( 11 | <> 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "version": "8.1.0-beta.0", 4 | "name": "example", 5 | "scripts": { 6 | "start": "next dev" 7 | }, 8 | "dependencies": { 9 | "@ethersproject/bignumber": "^5.6.0", 10 | "@ethersproject/experimental": "^5.6.0", 11 | "@ethersproject/providers": "^5.6.0", 12 | "@ethersproject/units": "^5.6.0", 13 | "@web3-react/coinbase-wallet": "^8.1.0-beta.0", 14 | "@web3-react/core": "^8.1.0-beta.0", 15 | "@web3-react/eip1193": "^8.1.0-beta.0", 16 | "@web3-react/empty": "^8.1.0-beta.0", 17 | "@web3-react/gnosis-safe": "^8.1.0-beta.0", 18 | "@web3-react/metamask": "^8.1.0-beta.0", 19 | "@web3-react/network": "^8.1.0-beta.0", 20 | "@web3-react/types": "^8.1.0-beta.0", 21 | "@web3-react/url": "^8.1.0-beta.0", 22 | "@web3-react/walletconnect-v2": "^8.1.0-beta.0", 23 | "@web3-react/walletconnect": "^8.1.0-beta.0", 24 | "next": "^12.1.5", 25 | "react-dom": "^18.0.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/walletconnect/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@web3-react/walletconnect", 3 | "keywords": [ 4 | "web3-react", 5 | "walletconnect" 6 | ], 7 | "author": "Noah Zinsmeister ", 8 | "license": "GPL-3.0-or-later", 9 | "repository": "github:Uniswap/web3-react", 10 | "publishConfig": { 11 | "access": "public" 12 | }, 13 | "version": "8.1.0-beta.0", 14 | "files": [ 15 | "dist/*" 16 | ], 17 | "type": "commonjs", 18 | "types": "./dist/index.d.ts", 19 | "main": "./dist/index.js", 20 | "exports": "./dist/index.js", 21 | "scripts": { 22 | "prebuild": "rm -rf dist", 23 | "build": "tsc", 24 | "start": "tsc --watch" 25 | }, 26 | "dependencies": { 27 | "@web3-react/types": "^8.1.0-beta.0", 28 | "eventemitter3": "^4.0.7", 29 | "@walletconnect/ethereum-provider": "^1.7.8" 30 | }, 31 | "devDependencies": { 32 | "@web3-react/store": "^8.1.0-beta.0", 33 | "@walletconnect/types": "^1.7.8" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /example/connectors/eip1193.ts: -------------------------------------------------------------------------------- 1 | import { Eip1193Bridge } from '@ethersproject/experimental' 2 | import { JsonRpcProvider } from '@ethersproject/providers' 3 | import { initializeConnector } from '@web3-react/core' 4 | import { EIP1193 } from '@web3-react/eip1193' 5 | 6 | import { URLS } from '../chains' 7 | 8 | class Eip1193BridgeWithoutAccounts extends Eip1193Bridge { 9 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 10 | request(request: { method: string; params?: any[] }): Promise { 11 | if (request.method === 'eth_requestAccounts' || request.method === 'eth_accounts') return Promise.resolve([]) 12 | return super.request(request) 13 | } 14 | } 15 | 16 | const ethersProvider = new JsonRpcProvider(URLS[1][0], 1) 17 | const eip1193Provider = new Eip1193BridgeWithoutAccounts(ethersProvider.getSigner(), ethersProvider) 18 | 19 | export const [eip1193, hooks] = initializeConnector( 20 | (actions) => new EIP1193({ actions, provider: eip1193Provider }) 21 | ) 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "web3-react", 4 | "engines": { 5 | "node": "^14 || ^16" 6 | }, 7 | "scripts": { 8 | "clean": "lerna clean --yes", 9 | "lint": "yarn run eslint --ext .ts,.tsx .", 10 | "test": "jest", 11 | "build": "lerna run build", 12 | "prestart": "yarn build", 13 | "start": "lerna run start --parallel", 14 | "prepack": "yarn build" 15 | }, 16 | "workspaces": [ 17 | "packages/*", 18 | "example" 19 | ], 20 | "devDependencies": { 21 | "@coinbase/wallet-sdk": "^3.3.0", 22 | "@testing-library/react-hooks": "^8.0.0", 23 | "@tsconfig/recommended": "^1.0.1", 24 | "@types/jest": "^27.4.1", 25 | "@types/node": "^17.0.24", 26 | "@types/react": "^18.0.5", 27 | "@typescript-eslint/eslint-plugin": "^5.19.0", 28 | "@typescript-eslint/parser": "^5.19.0", 29 | "@uniswap/eslint-config": "^1.1.1", 30 | "eslint": "^8.13.0", 31 | "eth-provider": "^0.9.4", 32 | "jest": "^27.5.1", 33 | "lerna": "^4.0.0", 34 | "react": "^18.0.0", 35 | "react-test-renderer": "^18.0.0", 36 | "ts-jest": "^27.1.4", 37 | "typescript": "^4.6.3" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /example/components/connectorCards/MetaMaskCard.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | import { hooks, metaMask } from '../../connectors/metaMask' 4 | import { Card } from '../Card' 5 | 6 | const { useChainId, useAccounts, useIsActivating, useIsActive, useProvider, useENSNames } = hooks 7 | 8 | export default function MetaMaskCard() { 9 | const chainId = useChainId() 10 | const accounts = useAccounts() 11 | const isActivating = useIsActivating() 12 | 13 | const isActive = useIsActive() 14 | 15 | const provider = useProvider() 16 | const ENSNames = useENSNames(provider) 17 | 18 | const [error, setError] = useState(undefined) 19 | 20 | // attempt to connect eagerly on mount 21 | useEffect(() => { 22 | void metaMask.connectEagerly().catch(() => { 23 | console.debug('Failed to connect eagerly to metamask') 24 | }) 25 | }, []) 26 | 27 | return ( 28 | 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /example/components/connectorCards/GnosisSafeCard.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | import { gnosisSafe, hooks } from '../../connectors/gnosisSafe' 4 | import { Card } from '../Card' 5 | 6 | const { useChainId, useAccounts, useIsActivating, useIsActive, useProvider, useENSNames } = hooks 7 | 8 | export default function GnosisSafeCard() { 9 | const chainId = useChainId() 10 | const accounts = useAccounts() 11 | const isActivating = useIsActivating() 12 | 13 | const isActive = useIsActive() 14 | 15 | const provider = useProvider() 16 | const ENSNames = useENSNames(provider) 17 | 18 | const [error, setError] = useState(undefined) 19 | 20 | // attempt to connect eagerly on mount 21 | useEffect(() => { 22 | void gnosisSafe.connectEagerly().catch(() => { 23 | console.debug('Failed to connect eagerly to gnosis safe') 24 | }) 25 | }, []) 26 | 27 | return ( 28 | 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /example/components/connectorCards/CoinbaseWalletCard.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | import { coinbaseWallet, hooks } from '../../connectors/coinbaseWallet' 4 | import { Card } from '../Card' 5 | 6 | const { useChainId, useAccounts, useIsActivating, useIsActive, useProvider, useENSNames } = hooks 7 | 8 | export default function CoinbaseWalletCard() { 9 | const chainId = useChainId() 10 | const accounts = useAccounts() 11 | const isActivating = useIsActivating() 12 | 13 | const isActive = useIsActive() 14 | 15 | const provider = useProvider() 16 | const ENSNames = useENSNames(provider) 17 | 18 | const [error, setError] = useState(undefined) 19 | 20 | // attempt to connect eagerly on mount 21 | useEffect(() => { 22 | void coinbaseWallet.connectEagerly().catch(() => { 23 | console.debug('Failed to connect eagerly to coinbase wallet') 24 | }) 25 | }, []) 26 | 27 | return ( 28 | 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /example/components/connectorCards/NetworkCard.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | import { URLS } from '../../chains' 4 | import { hooks, network } from '../../connectors/network' 5 | import { Card } from '../Card' 6 | 7 | const { useChainId, useAccounts, useIsActivating, useIsActive, useProvider, useENSNames } = hooks 8 | 9 | const CHAIN_IDS = Object.keys(URLS).map(Number) 10 | 11 | export default function NetworkCard() { 12 | const chainId = useChainId() 13 | const accounts = useAccounts() 14 | const isActivating = useIsActivating() 15 | 16 | const isActive = useIsActive() 17 | 18 | const provider = useProvider() 19 | const ENSNames = useENSNames(provider) 20 | 21 | const [error, setError] = useState(undefined) 22 | 23 | // attempt to connect eagerly on mount 24 | useEffect(() => { 25 | void network.activate().catch(() => { 26 | console.debug('Failed to connect to network') 27 | }) 28 | }, []) 29 | 30 | return ( 31 | 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /example/components/connectorCards/WalletConnectCard.tsx: -------------------------------------------------------------------------------- 1 | import { URI_AVAILABLE } from '@web3-react/walletconnect' 2 | import { useEffect, useState } from 'react' 3 | 4 | import { hooks, walletConnect } from '../../connectors/walletConnect' 5 | import { Card } from '../Card' 6 | 7 | const { useChainId, useAccounts, useIsActivating, useIsActive, useProvider, useENSNames } = hooks 8 | 9 | export default function WalletConnectCard() { 10 | const chainId = useChainId() 11 | const accounts = useAccounts() 12 | const isActivating = useIsActivating() 13 | 14 | const isActive = useIsActive() 15 | 16 | const provider = useProvider() 17 | const ENSNames = useENSNames(provider) 18 | 19 | const [error, setError] = useState(undefined) 20 | 21 | // log URI when available 22 | useEffect(() => { 23 | walletConnect.events.on(URI_AVAILABLE, (uri: string) => { 24 | console.log(`uri: ${uri}`) 25 | }) 26 | }, []) 27 | 28 | // attempt to connect eagerly on mount 29 | useEffect(() => { 30 | walletConnect.connectEagerly().catch(() => { 31 | console.debug('Failed to connect eagerly to walletconnect') 32 | }) 33 | }, []) 34 | 35 | return ( 36 | 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /packages/eip1193/src/mock.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'node:events' 2 | 3 | import type { ProviderRpcError, RequestArguments } from '@web3-react/types' 4 | 5 | export class MockEIP1193Provider extends EventEmitter { 6 | public chainId?: string 7 | public accounts?: string[] 8 | 9 | public eth_chainId = jest.fn((chainId?: string) => chainId) 10 | public eth_accounts = jest.fn((accounts?: string[]) => accounts) 11 | public eth_requestAccounts = jest.fn((accounts?: string[]) => accounts) 12 | 13 | public request(x: RequestArguments): Promise { 14 | // make sure to throw if we're "not connected" 15 | if (!this.chainId) return Promise.reject(new Error()) 16 | 17 | switch (x.method) { 18 | case 'eth_chainId': 19 | return Promise.resolve(this.eth_chainId(this.chainId)) 20 | case 'eth_accounts': 21 | return Promise.resolve(this.eth_accounts(this.accounts)) 22 | case 'eth_requestAccounts': 23 | return Promise.resolve(this.eth_requestAccounts(this.accounts)) 24 | default: 25 | throw new Error() 26 | } 27 | } 28 | 29 | public emitConnect(chainId: string) { 30 | this.emit('connect', { chainId }) 31 | } 32 | 33 | public emitDisconnect(error: ProviderRpcError) { 34 | this.emit('disconnect', error) 35 | } 36 | 37 | public emitChainChanged(chainId: string) { 38 | this.emit('chainChanged', chainId) 39 | } 40 | 41 | public emitAccountsChanged(accounts: string[]) { 42 | this.emit('accountsChanged', accounts) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/components/ProviderExample.tsx: -------------------------------------------------------------------------------- 1 | import type { CoinbaseWallet } from '@web3-react/coinbase-wallet' 2 | import { useWeb3React, Web3ReactHooks, Web3ReactProvider } from '@web3-react/core' 3 | import type { MetaMask } from '@web3-react/metamask' 4 | import type { Network } from '@web3-react/network' 5 | import type { WalletConnect } from '@web3-react/walletconnect' 6 | import type { WalletConnect as WalletConnectV2 } from '@web3-react/walletconnect-v2' 7 | 8 | import { coinbaseWallet, hooks as coinbaseWalletHooks } from '../connectors/coinbaseWallet' 9 | import { hooks as metaMaskHooks, metaMask } from '../connectors/metaMask' 10 | import { hooks as networkHooks, network } from '../connectors/network' 11 | import { hooks as walletConnectHooks, walletConnect } from '../connectors/walletConnect' 12 | import { hooks as walletConnectV2Hooks, walletConnectV2 } from '../connectors/walletConnectV2' 13 | import { getName } from '../utils' 14 | 15 | const connectors: [MetaMask | WalletConnect | WalletConnectV2 | CoinbaseWallet | Network, Web3ReactHooks][] = [ 16 | [metaMask, metaMaskHooks], 17 | [walletConnect, walletConnectHooks], 18 | [walletConnectV2, walletConnectV2Hooks], 19 | [coinbaseWallet, coinbaseWalletHooks], 20 | [network, networkHooks], 21 | ] 22 | 23 | function Child() { 24 | const { connector } = useWeb3React() 25 | console.log(`Priority Connector is: ${getName(connector)}`) 26 | return null 27 | } 28 | 29 | export default function ProviderExample() { 30 | return ( 31 | 32 | 33 | 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /example/components/connectorCards/WalletConnectV2Card.tsx: -------------------------------------------------------------------------------- 1 | import { URI_AVAILABLE } from '@web3-react/walletconnect-v2' 2 | import { useEffect, useState } from 'react' 3 | 4 | import { MAINNET_CHAINS } from '../../chains' 5 | import { hooks, walletConnectV2 } from '../../connectors/walletConnectV2' 6 | import { Card } from '../Card' 7 | 8 | const CHAIN_IDS = Object.keys(MAINNET_CHAINS).map(Number) 9 | 10 | const { useChainId, useAccounts, useIsActivating, useIsActive, useProvider, useENSNames } = hooks 11 | 12 | export default function WalletConnectV2Card() { 13 | const chainId = useChainId() 14 | const accounts = useAccounts() 15 | const isActivating = useIsActivating() 16 | 17 | const isActive = useIsActive() 18 | 19 | const provider = useProvider() 20 | const ENSNames = useENSNames(provider) 21 | 22 | const [error, setError] = useState(undefined) 23 | 24 | // log URI when available 25 | useEffect(() => { 26 | walletConnectV2.events.on(URI_AVAILABLE, (uri: string) => { 27 | console.log(`uri: ${uri}`) 28 | }) 29 | }, []) 30 | 31 | // attempt to connect eagerly on mount 32 | useEffect(() => { 33 | walletConnectV2.connectEagerly().catch((error) => { 34 | console.debug('Failed to connect eagerly to walletconnect', error) 35 | }) 36 | }, []) 37 | 38 | return ( 39 | 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /packages/url/src/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { createWeb3ReactStoreAndActions } from '@web3-react/store' 2 | import type { Actions, Web3ReactStore } from '@web3-react/types' 3 | import { Url } from '.' 4 | import { MockJsonRpcProvider } from '../../network/src/index.spec' 5 | 6 | jest.mock('@ethersproject/providers', () => ({ 7 | JsonRpcProvider: MockJsonRpcProvider, 8 | })) 9 | 10 | const chainId = '0x1' 11 | const accounts: string[] = [] 12 | 13 | describe('Url', () => { 14 | let store: Web3ReactStore 15 | let connector: Url 16 | let mockConnector: MockJsonRpcProvider 17 | 18 | describe('works', () => { 19 | beforeEach(() => { 20 | let actions: Actions 21 | ;[store, actions] = createWeb3ReactStoreAndActions() 22 | connector = new Url({ actions, url: 'https://mock.url' }) 23 | }) 24 | 25 | test('is un-initialized', async () => { 26 | expect(store.getState()).toEqual({ 27 | chainId: undefined, 28 | accounts: undefined, 29 | activating: false, 30 | error: undefined, 31 | }) 32 | }) 33 | 34 | describe('#activate', () => { 35 | beforeEach(async () => { 36 | // testing hack to ensure the provider is set 37 | await connector.activate() 38 | mockConnector = connector.customProvider as unknown as MockJsonRpcProvider 39 | mockConnector.chainId = chainId 40 | }) 41 | 42 | test('works', async () => { 43 | await connector.activate() 44 | 45 | expect(store.getState()).toEqual({ 46 | chainId: Number.parseInt(chainId, 16), 47 | accounts, 48 | activating: false, 49 | error: undefined, 50 | }) 51 | }) 52 | }) 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /packages/url/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { JsonRpcProvider } from '@ethersproject/providers' 2 | import type { ConnectionInfo } from '@ethersproject/web' 3 | import type { Actions } from '@web3-react/types' 4 | import { Connector } from '@web3-react/types' 5 | 6 | type url = string | ConnectionInfo 7 | 8 | function isUrl(url: url | JsonRpcProvider): url is url { 9 | return typeof url === 'string' || ('url' in url && !('connection' in url)) 10 | } 11 | 12 | /** 13 | * @param url - An RPC url or a JsonRpcProvider. 14 | */ 15 | export interface UrlConstructorArgs { 16 | actions: Actions 17 | url: url | JsonRpcProvider 18 | } 19 | 20 | export class Url extends Connector { 21 | /** {@inheritdoc Connector.provider} */ 22 | public readonly provider: undefined 23 | /** {@inheritdoc Connector.customProvider} */ 24 | public customProvider?: JsonRpcProvider 25 | 26 | private readonly url: url | JsonRpcProvider 27 | 28 | constructor({ actions, url }: UrlConstructorArgs) { 29 | super(actions) 30 | this.url = url 31 | } 32 | 33 | /** {@inheritdoc Connector.activate} */ 34 | public async activate(): Promise { 35 | if (!this.customProvider) { 36 | const cancelActivation = this.actions.startActivation() 37 | if (!isUrl(this.url)) this.customProvider = this.url 38 | await import('@ethersproject/providers') 39 | .then(({ JsonRpcProvider }) => { 40 | this.customProvider = new JsonRpcProvider(this.url as url) 41 | }) 42 | .catch((error) => { 43 | cancelActivation() 44 | throw error 45 | }) 46 | } 47 | 48 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 49 | const { chainId } = await this.customProvider!.getNetwork() 50 | this.actions.update({ chainId, accounts: [] }) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /example/components/Accounts.tsx: -------------------------------------------------------------------------------- 1 | import type { BigNumber } from '@ethersproject/bignumber' 2 | import { formatEther } from '@ethersproject/units' 3 | import type { Web3ReactHooks } from '@web3-react/core' 4 | import { useEffect, useState } from 'react' 5 | 6 | function useBalances( 7 | provider?: ReturnType, 8 | accounts?: string[] 9 | ): BigNumber[] | undefined { 10 | const [balances, setBalances] = useState() 11 | 12 | useEffect(() => { 13 | if (provider && accounts?.length) { 14 | let stale = false 15 | 16 | void Promise.all(accounts.map((account) => provider.getBalance(account))).then((balances) => { 17 | if (stale) return 18 | setBalances(balances) 19 | }) 20 | 21 | return () => { 22 | stale = true 23 | setBalances(undefined) 24 | } 25 | } 26 | }, [provider, accounts]) 27 | 28 | return balances 29 | } 30 | 31 | export function Accounts({ 32 | accounts, 33 | provider, 34 | ENSNames, 35 | }: { 36 | accounts: ReturnType 37 | provider: ReturnType 38 | ENSNames: ReturnType 39 | }) { 40 | const balances = useBalances(provider, accounts) 41 | 42 | if (accounts === undefined) return null 43 | 44 | return ( 45 |
46 | Accounts:{' '} 47 | 48 | {accounts.length === 0 49 | ? 'None' 50 | : accounts?.map((account, i) => ( 51 |
    52 | {ENSNames?.[i] ?? account} 53 | {balances?.[i] ? ` (Ξ${formatEther(balances[i])})` : null} 54 |
55 | ))} 56 |
57 |
58 | ) 59 | } 60 | -------------------------------------------------------------------------------- /packages/coinbase-wallet/src/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { createWeb3ReactStoreAndActions } from '@web3-react/store' 2 | import type { Actions, Web3ReactStore } from '@web3-react/types' 3 | import { CoinbaseWallet } from '.' 4 | import { MockEIP1193Provider } from '../../eip1193/src/mock' 5 | 6 | jest.mock( 7 | '@coinbase/wallet-sdk', 8 | () => 9 | class MockCoinbaseWallet { 10 | makeWeb3Provider() { 11 | return new MockEIP1193Provider() 12 | } 13 | } 14 | ) 15 | 16 | const chainId = '0x1' 17 | const accounts: string[] = [] 18 | 19 | describe('Coinbase Wallet', () => { 20 | let store: Web3ReactStore 21 | let connector: CoinbaseWallet 22 | let mockProvider: MockEIP1193Provider 23 | 24 | describe('connectEagerly = true', () => { 25 | beforeEach(async () => { 26 | let actions: Actions 27 | ;[store, actions] = createWeb3ReactStoreAndActions() 28 | connector = new CoinbaseWallet({ 29 | actions, 30 | options: { 31 | appName: 'test', 32 | url: 'https://mock.url', 33 | }, 34 | }) 35 | await connector.connectEagerly().catch(() => {}) 36 | 37 | mockProvider = connector.provider as unknown as MockEIP1193Provider 38 | mockProvider.chainId = chainId 39 | mockProvider.accounts = accounts 40 | }) 41 | 42 | test('#activate', async () => { 43 | await connector.activate() 44 | 45 | expect(mockProvider.eth_requestAccounts).toHaveBeenCalled() 46 | expect(mockProvider.eth_accounts).not.toHaveBeenCalled() 47 | expect(mockProvider.eth_chainId).toHaveBeenCalled() 48 | expect(mockProvider.eth_chainId.mock.invocationCallOrder[0]) 49 | .toBeGreaterThan(mockProvider.eth_requestAccounts.mock.invocationCallOrder[0]) 50 | 51 | expect(store.getState()).toEqual({ 52 | chainId: Number.parseInt(chainId, 16), 53 | accounts, 54 | activating: false, 55 | }) 56 | }) 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /packages/metamask/src/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { createWeb3ReactStoreAndActions } from '@web3-react/store' 2 | import type { Actions, Web3ReactStore } from '@web3-react/types' 3 | import { MetaMask } from '.' 4 | import { MockEIP1193Provider } from '../../eip1193/src/mock' 5 | 6 | const chainId = '0x1' 7 | const accounts: string[] = ['0x0000000000000000000000000000000000000000'] 8 | 9 | describe('MetaMask', () => { 10 | let mockProvider: MockEIP1193Provider 11 | 12 | beforeEach(() => { 13 | mockProvider = new MockEIP1193Provider() 14 | }) 15 | 16 | beforeEach(() => { 17 | ;(window as any).ethereum = mockProvider 18 | }) 19 | 20 | let store: Web3ReactStore 21 | let connector: MetaMask 22 | 23 | beforeEach(() => { 24 | let actions: Actions 25 | ;[store, actions] = createWeb3ReactStoreAndActions() 26 | connector = new MetaMask({ actions }) 27 | }) 28 | 29 | test('#connectEagerly', async () => { 30 | mockProvider.chainId = chainId 31 | mockProvider.accounts = accounts 32 | 33 | await connector.connectEagerly() 34 | 35 | expect(mockProvider.eth_requestAccounts).not.toHaveBeenCalled() 36 | expect(mockProvider.eth_accounts).toHaveBeenCalled() 37 | expect(mockProvider.eth_chainId).toHaveBeenCalled() 38 | expect(mockProvider.eth_chainId.mock.invocationCallOrder[0]) 39 | .toBeGreaterThan(mockProvider.eth_accounts.mock.invocationCallOrder[0]) 40 | 41 | expect(store.getState()).toEqual({ 42 | chainId: Number.parseInt(chainId, 16), 43 | accounts, 44 | activating: false, 45 | }) 46 | }) 47 | 48 | test('#activate', async () => { 49 | mockProvider.chainId = chainId 50 | mockProvider.accounts = accounts 51 | 52 | await connector.activate() 53 | 54 | expect(mockProvider.eth_requestAccounts).toHaveBeenCalled() 55 | expect(mockProvider.eth_accounts).not.toHaveBeenCalled() 56 | expect(mockProvider.eth_chainId).toHaveBeenCalled() 57 | expect(mockProvider.eth_chainId.mock.invocationCallOrder[0]) 58 | .toBeGreaterThan(mockProvider.eth_requestAccounts.mock.invocationCallOrder[0]) 59 | 60 | expect(store.getState()).toEqual({ 61 | chainId: Number.parseInt(chainId, 16), 62 | accounts, 63 | activating: false, 64 | }) 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /packages/walletconnect/src/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import { getBestUrl } from './utils' 2 | 3 | class MockHttpConnection { 4 | public readonly succeed: boolean 5 | public readonly latency: number 6 | 7 | constructor(url: string) { 8 | this.succeed = url.startsWith('succeed') 9 | this.latency = Number.parseInt(url.split('_')[1]) 10 | } 11 | } 12 | 13 | class MockJsonRpcProvider { 14 | private readonly http: MockHttpConnection 15 | 16 | constructor(http: MockHttpConnection) { 17 | this.http = http 18 | } 19 | 20 | public async request() { 21 | return new Promise((resolve, reject) => { 22 | setTimeout(() => { 23 | if (this.http.succeed) { 24 | resolve(1) 25 | } else { 26 | reject() 27 | } 28 | }, this.http.latency) 29 | }) 30 | } 31 | } 32 | 33 | jest.mock('@walletconnect/jsonrpc-http-connection', () => ({ 34 | HttpConnection: MockHttpConnection, 35 | })) 36 | jest.mock('@walletconnect/jsonrpc-provider', () => ({ 37 | JsonRpcProvider: MockJsonRpcProvider, 38 | })) 39 | 40 | describe('getBestUrl', () => { 41 | test('works with 1 url (success)', async () => { 42 | const url = await getBestUrl(['succeed_0'], 100) 43 | expect(url).toBe('succeed_0') 44 | }) 45 | 46 | test('works with 1 url (failure)', async () => { 47 | const url = await getBestUrl(['fail_0'], 100) 48 | expect(url).toBe('fail_0') 49 | }) 50 | 51 | test('works with 2 urls (success/failure)', async () => { 52 | const url = await getBestUrl(['succeed_0', 'fail_0'], 100) 53 | expect(url).toBe('succeed_0') 54 | }) 55 | 56 | test('works with 2 urls (failure/success)', async () => { 57 | const url = await getBestUrl(['fail_0', 'succeed_0'], 100) 58 | expect(url).toBe('succeed_0') 59 | }) 60 | 61 | test('works with 2 successful urls (fast/slow)', async () => { 62 | const url = await getBestUrl(['succeed_0', 'succeed_1'], 100) 63 | expect(url).toBe('succeed_0') 64 | }) 65 | 66 | test('works with 2 successful urls (slow/fast)', async () => { 67 | const url = await getBestUrl(['succeed_1', 'succeed_0'], 100) 68 | expect(url).toBe('succeed_1') 69 | }) 70 | 71 | test('works with 2 successful urls (after timeout/before timeout)', async () => { 72 | const url = await getBestUrl(['succeed_100', 'succeed_0'], 50) 73 | expect(url).toBe('succeed_0') 74 | }) 75 | }) 76 | -------------------------------------------------------------------------------- /packages/walletconnect/src/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { createWeb3ReactStoreAndActions } from '@web3-react/store' 2 | import type { Actions, RequestArguments, Web3ReactStore } from '@web3-react/types' 3 | import EventEmitter from 'node:events' 4 | import { WalletConnect } from '.' 5 | import { MockEIP1193Provider } from '../../eip1193/src/mock' 6 | 7 | // necessary because walletconnect returns chainId as a number 8 | class MockMockWalletConnectProvider extends MockEIP1193Provider { 9 | public connector = new EventEmitter() 10 | 11 | public eth_chainId_number = jest.fn((chainId?: string) => 12 | chainId === undefined ? chainId : Number.parseInt(chainId, 16) 13 | ) 14 | 15 | public request(x: RequestArguments): Promise { 16 | if (x.method === 'eth_chainId') { 17 | return Promise.resolve(this.eth_chainId_number(this.chainId)) 18 | } else { 19 | return super.request(x) 20 | } 21 | } 22 | } 23 | 24 | jest.mock('@walletconnect/ethereum-provider', () => MockMockWalletConnectProvider) 25 | 26 | const chainId = '0x1' 27 | const accounts: string[] = [] 28 | 29 | describe('WalletConnect', () => { 30 | let store: Web3ReactStore 31 | let connector: WalletConnect 32 | let mockProvider: MockMockWalletConnectProvider 33 | 34 | describe('works', () => { 35 | beforeEach(async () => { 36 | let actions: Actions 37 | ;[store, actions] = createWeb3ReactStoreAndActions() 38 | connector = new WalletConnect({ actions, options: { rpc: {} } }) 39 | }) 40 | 41 | test('#activate', async () => { 42 | await connector.connectEagerly().catch(() => {}) 43 | 44 | mockProvider = connector.provider as unknown as MockMockWalletConnectProvider 45 | mockProvider.chainId = chainId 46 | mockProvider.accounts = accounts 47 | 48 | await connector.activate() 49 | 50 | expect(mockProvider.eth_requestAccounts).toHaveBeenCalled() 51 | expect(mockProvider.eth_accounts).not.toHaveBeenCalled() 52 | expect(mockProvider.eth_chainId_number).toHaveBeenCalled() 53 | expect(mockProvider.eth_chainId_number.mock.invocationCallOrder[0]) 54 | .toBeGreaterThan(mockProvider.eth_requestAccounts.mock.invocationCallOrder[0]) 55 | 56 | expect(store.getState()).toEqual({ 57 | chainId: Number.parseInt(chainId, 16), 58 | accounts, 59 | activating: false, 60 | error: undefined, 61 | }) 62 | }) 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /example/components/Card.tsx: -------------------------------------------------------------------------------- 1 | import type { CoinbaseWallet } from '@web3-react/coinbase-wallet' 2 | import type { Web3ReactHooks } from '@web3-react/core' 3 | import type { GnosisSafe } from '@web3-react/gnosis-safe' 4 | import type { MetaMask } from '@web3-react/metamask' 5 | import type { Network } from '@web3-react/network' 6 | import type { WalletConnect } from '@web3-react/walletconnect' 7 | import type { WalletConnect as WalletConnectV2 } from '@web3-react/walletconnect-v2' 8 | 9 | import { getName } from '../utils' 10 | import { Accounts } from './Accounts' 11 | import { Chain } from './Chain' 12 | import { ConnectWithSelect } from './ConnectWithSelect' 13 | import { Status } from './Status' 14 | 15 | interface Props { 16 | connector: MetaMask | WalletConnect | WalletConnectV2 | CoinbaseWallet | Network | GnosisSafe 17 | activeChainId: ReturnType 18 | chainIds?: ReturnType[] 19 | isActivating: ReturnType 20 | isActive: ReturnType 21 | error: Error | undefined 22 | setError: (error: Error | undefined) => void 23 | ENSNames: ReturnType 24 | provider?: ReturnType 25 | accounts?: string[] 26 | } 27 | 28 | export function Card({ 29 | connector, 30 | activeChainId, 31 | chainIds, 32 | isActivating, 33 | isActive, 34 | error, 35 | setError, 36 | ENSNames, 37 | accounts, 38 | provider, 39 | }: Props) { 40 | return ( 41 |
54 | {getName(connector)} 55 |
56 | 57 |
58 | 59 |
60 | 61 |
62 | 71 |
72 | ) 73 | } 74 | -------------------------------------------------------------------------------- /packages/network/src/utils.ts: -------------------------------------------------------------------------------- 1 | import type { JsonRpcProvider } from '@ethersproject/providers' 2 | 3 | /** 4 | * @param providers - An array of providers to try to connect to. 5 | * @param timeout - How long to wait before a call is considered failed, in ms. 6 | */ 7 | export async function getBestProvider(providers: JsonRpcProvider[], timeout = 5000): Promise { 8 | // if we only have 1 provider, it's the best! 9 | if (providers.length === 1) return providers[0] 10 | 11 | // the below returns the first provider for which there's been a successful call, prioritized by index 12 | return new Promise((resolve) => { 13 | let resolved = false 14 | const successes: { [index: number]: boolean } = {} 15 | 16 | providers.forEach((provider, i) => { 17 | // create a promise that resolves on a successful call, and rejects on a failed call or after timeout milliseconds 18 | const promise = new Promise((resolve, reject) => { 19 | provider 20 | .getNetwork() 21 | .then(() => resolve()) 22 | .catch(() => reject()) 23 | 24 | // set a timeout to reject 25 | setTimeout(() => { 26 | reject() 27 | }, timeout) 28 | }) 29 | 30 | void promise 31 | .then(() => true) 32 | .catch(() => false) 33 | .then((success) => { 34 | // if we already resolved, return 35 | if (resolved) return 36 | 37 | // store the result of the call 38 | successes[i] = success 39 | 40 | // if this is the last call and we haven't resolved yet - do so 41 | if (Object.keys(successes).length === providers.length) { 42 | const index = Object.keys(successes).findIndex((j) => successes[Number(j)]) 43 | // no need to set resolved to true, as this is the last promise 44 | return resolve(providers[index === -1 ? 0 : index]) 45 | } 46 | 47 | // otherwise, for each prospective index, check if we can resolve 48 | new Array(providers.length).fill(0).forEach((_, prospectiveIndex) => { 49 | // to resolve, we need to: 50 | // a) have successfully made a call 51 | // b) not be waiting on any other higher-index calls 52 | if ( 53 | successes[prospectiveIndex] && 54 | new Array(prospectiveIndex).fill(0).every((_, j) => successes[j] === false) 55 | ) { 56 | resolved = true 57 | resolve(providers[prospectiveIndex]) 58 | } 59 | }) 60 | }) 61 | }) 62 | }) 63 | } 64 | -------------------------------------------------------------------------------- /packages/walletconnect-v2/src/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import { getBestUrlMap, getChainsWithDefault } from './utils' 2 | 3 | class MockHttpConnection { 4 | public readonly succeed: boolean 5 | public readonly latency: number 6 | 7 | constructor(url: string) { 8 | this.succeed = url.startsWith('succeed') 9 | this.latency = Number.parseInt(url.split('_')[1]) 10 | } 11 | } 12 | 13 | class MockJsonRpcProvider { 14 | private readonly http: MockHttpConnection 15 | 16 | constructor(http: MockHttpConnection) { 17 | this.http = http 18 | } 19 | 20 | public async request() { 21 | return new Promise((resolve, reject) => { 22 | setTimeout(() => { 23 | if (this.http.succeed) { 24 | resolve(1) 25 | } else { 26 | reject() 27 | } 28 | }, this.http.latency) 29 | }) 30 | } 31 | } 32 | 33 | jest.mock('@walletconnect/jsonrpc-http-connection', () => ({ 34 | HttpConnection: MockHttpConnection, 35 | })) 36 | jest.mock('@walletconnect/jsonrpc-provider', () => ({ 37 | JsonRpcProvider: MockJsonRpcProvider, 38 | })) 39 | 40 | describe('getBestUrl', () => { 41 | test('works with a single string', async () => { 42 | const rpc = await getBestUrlMap({0: 'succeed_0'}, 100) 43 | expect(rpc[0]).toBe('succeed_0') 44 | }) 45 | 46 | test('works with 1 rpc (success)', async () => { 47 | const rpc = await getBestUrlMap({0: ['succeed_0']}, 100) 48 | expect(rpc[0]).toBe('succeed_0') 49 | }) 50 | 51 | test('works with 2 urls (success/failure)', async () => { 52 | const rpc = await getBestUrlMap({0: ['succeed_0', 'fail_0']}, 100) 53 | expect(rpc[0]).toBe('succeed_0') 54 | }) 55 | 56 | test('works with 2 urls (failure/success)', async () => { 57 | const rpc = await getBestUrlMap({0: ['fail_0', 'succeed_0']}, 100) 58 | expect(rpc[0]).toBe('succeed_0') 59 | }) 60 | 61 | test('works with 2 successful urls (fast/slow)', async () => { 62 | const rpc = await getBestUrlMap({0: ['succeed_0', 'succeed_1']}, 100) 63 | expect(rpc[0]).toBe('succeed_0') 64 | }) 65 | 66 | test('works with 2 successful urls (slow/fast)', async () => { 67 | const rpc = await getBestUrlMap({0: ['succeed_1', 'succeed_0']}, 100) 68 | expect(rpc[0]).toBe('succeed_1') 69 | }) 70 | 71 | test('works with 2 successful urls (after timeout/before timeout)', async () => { 72 | const rpc = await getBestUrlMap({0: ['succeed_100', 'succeed_0']}, 50) 73 | expect(rpc[0]).toBe('succeed_0') 74 | }) 75 | }) 76 | 77 | describe('getChainsWithDefault', () => { 78 | test('puts the default chain first at the beginning', () => { 79 | expect(getChainsWithDefault([1, 2, 3], 3)).toEqual([3, 1, 2]) 80 | }) 81 | }) 82 | -------------------------------------------------------------------------------- /packages/walletconnect/src/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @param urls - An array of URLs to try to connect to. 3 | * @param timeout - How long to wait before a call is considered failed, in ms. 4 | */ 5 | export async function getBestUrl(urls: string[], timeout: number): Promise { 6 | // if we only have 1 url, it's the best! 7 | if (urls.length === 1) return urls[0] 8 | 9 | const [HttpConnection, JsonRpcProvider] = await Promise.all([ 10 | import('@walletconnect/jsonrpc-http-connection').then(({ HttpConnection }) => HttpConnection), 11 | import('@walletconnect/jsonrpc-provider').then(({ JsonRpcProvider }) => JsonRpcProvider), 12 | ]) 13 | 14 | // the below returns the first url for which there's been a successful call, prioritized by index 15 | return new Promise((resolve) => { 16 | let resolved = false 17 | const successes: { [index: number]: boolean } = {} 18 | 19 | urls.forEach((url, i) => { 20 | const http = new JsonRpcProvider(new HttpConnection(url)) 21 | 22 | // create a promise that resolves on a successful call, and rejects on a failed call or after timeout milliseconds 23 | const promise = new Promise((resolve, reject) => { 24 | http 25 | .request({ method: 'eth_chainId' }) 26 | .then(() => resolve()) 27 | .catch(() => reject()) 28 | 29 | // set a timeout to reject 30 | setTimeout(() => { 31 | reject() 32 | }, timeout) 33 | }) 34 | 35 | void promise 36 | .then(() => true) 37 | .catch(() => false) 38 | .then((success) => { 39 | // if we already resolved, return 40 | if (resolved) return 41 | 42 | // store the result of the call 43 | successes[i] = success 44 | 45 | // if this is the last call and we haven't resolved yet - do so 46 | if (Object.keys(successes).length === urls.length) { 47 | const index = Object.keys(successes).findIndex((j) => successes[Number(j)]) 48 | // no need to set resolved to true, as this is the last promise 49 | return resolve(urls[index === -1 ? 0 : index]) 50 | } 51 | 52 | // otherwise, for each prospective index, check if we can resolve 53 | new Array(urls.length).fill(0).forEach((_, prospectiveIndex) => { 54 | // to resolve, we need to: 55 | // a) have successfully made a call 56 | // b) not be waiting on any other higher-index calls 57 | if ( 58 | successes[prospectiveIndex] && 59 | new Array(prospectiveIndex).fill(0).every((_, j) => successes[j] === false) 60 | ) { 61 | resolved = true 62 | resolve(urls[prospectiveIndex]) 63 | } 64 | }) 65 | }) 66 | }) 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /packages/eip1193/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { Actions, Provider, ProviderConnectInfo, ProviderRpcError } from '@web3-react/types' 2 | import { Connector } from '@web3-react/types' 3 | 4 | function parseChainId(chainId: string | number) { 5 | return typeof chainId === 'string' ? Number.parseInt(chainId, 16) : chainId 6 | } 7 | 8 | /** 9 | * @param provider - An EIP-1193 ({@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md}) provider. 10 | * @param onError - Handler to report errors thrown from eventListeners. 11 | */ 12 | export interface EIP1193ConstructorArgs { 13 | actions: Actions 14 | provider: Provider 15 | onError?: (error: Error) => void 16 | } 17 | 18 | export class EIP1193 extends Connector { 19 | /** {@inheritdoc Connector.provider} */ 20 | provider: Provider 21 | 22 | constructor({ actions, provider, onError }: EIP1193ConstructorArgs) { 23 | super(actions, onError) 24 | 25 | this.provider = provider 26 | 27 | this.provider.on('connect', ({ chainId }: ProviderConnectInfo): void => { 28 | this.actions.update({ chainId: parseChainId(chainId) }) 29 | }) 30 | 31 | this.provider.on('disconnect', (error: ProviderRpcError): void => { 32 | this.actions.resetState() 33 | this.onError?.(error) 34 | }) 35 | 36 | this.provider.on('chainChanged', (chainId: string): void => { 37 | this.actions.update({ chainId: parseChainId(chainId) }) 38 | }) 39 | 40 | this.provider.on('accountsChanged', (accounts: string[]): void => { 41 | this.actions.update({ accounts }) 42 | }) 43 | } 44 | 45 | private async activateAccounts(requestAccounts: () => Promise): Promise { 46 | const cancelActivation = this.actions.startActivation() 47 | 48 | try { 49 | // Wallets may resolve eth_chainId and hang on eth_accounts pending user interaction, which may include changing 50 | // chains; they should be requested serially, with accounts first, so that the chainId can settle. 51 | const accounts = await requestAccounts() 52 | const chainId = (await this.provider.request({ method: 'eth_chainId' })) as string 53 | this.actions.update({ chainId: parseChainId(chainId), accounts }) 54 | } catch (error) { 55 | cancelActivation() 56 | throw error 57 | } 58 | } 59 | 60 | /** {@inheritdoc Connector.connectEagerly} */ 61 | public async connectEagerly(): Promise { 62 | return this.activateAccounts(() => this.provider.request({ method: 'eth_accounts' }) as Promise) 63 | } 64 | 65 | /** {@inheritdoc Connector.activate} */ 66 | public async activate(): Promise { 67 | return this.activateAccounts( 68 | () => 69 | this.provider 70 | .request({ method: 'eth_requestAccounts' }) 71 | .catch(() => this.provider.request({ method: 'eth_accounts' })) as Promise 72 | ) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/network/src/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import type { JsonRpcProvider } from '@ethersproject/providers' 2 | import { getBestProvider } from './utils' 3 | 4 | class MockJsonRpcProvider { 5 | public readonly succeed: boolean 6 | public readonly latency: number 7 | 8 | constructor(succeed: boolean, latency: number) { 9 | this.succeed = succeed 10 | this.latency = latency 11 | } 12 | 13 | public async getNetwork() { 14 | return new Promise((resolve, reject) => { 15 | setTimeout(() => { 16 | if (this.succeed) { 17 | resolve(1) 18 | } else { 19 | reject() 20 | } 21 | }, this.latency) 22 | }) 23 | } 24 | } 25 | 26 | describe('getBestProvider', () => { 27 | test('works with 1 url (success)', async () => { 28 | const provider = new MockJsonRpcProvider(true, 0) as unknown as JsonRpcProvider 29 | const url = await getBestProvider([provider]) 30 | expect(url === provider).toBe(true) 31 | }) 32 | 33 | test('works with 1 url (failure)', async () => { 34 | const provider = new MockJsonRpcProvider(false, 0) as unknown as JsonRpcProvider 35 | const url = await getBestProvider([provider]) 36 | expect(url === provider).toBe(true) 37 | }) 38 | 39 | test('works with 2 urls (success/failure)', async () => { 40 | const providerSucceed = new MockJsonRpcProvider(true, 0) as unknown as JsonRpcProvider 41 | const providerFail = new MockJsonRpcProvider(false, 0) as unknown as JsonRpcProvider 42 | 43 | const url = await getBestProvider([providerSucceed, providerFail]) 44 | expect(url === providerSucceed).toBe(true) 45 | }) 46 | 47 | test('works with 2 urls (failure/success)', async () => { 48 | const providerFail = new MockJsonRpcProvider(false, 0) as unknown as JsonRpcProvider 49 | const providerSucceed = new MockJsonRpcProvider(true, 0) as unknown as JsonRpcProvider 50 | 51 | const url = await getBestProvider([providerFail, providerSucceed]) 52 | expect(url === providerSucceed).toBe(true) 53 | }) 54 | 55 | test('works with 2 successful urls (fast/slow)', async () => { 56 | const provider0 = new MockJsonRpcProvider(true, 0) as unknown as JsonRpcProvider 57 | const provider1 = new MockJsonRpcProvider(true, 1) as unknown as JsonRpcProvider 58 | 59 | const url = await getBestProvider([provider0, provider1]) 60 | expect(url === provider0).toBe(true) 61 | }) 62 | 63 | test('works with 2 successful urls (slow/fast)', async () => { 64 | const provider1 = new MockJsonRpcProvider(true, 1) as unknown as JsonRpcProvider 65 | const provider0 = new MockJsonRpcProvider(true, 0) as unknown as JsonRpcProvider 66 | 67 | const url = await getBestProvider([provider1, provider0]) 68 | expect(url === provider1).toBe(true) 69 | }) 70 | 71 | test('works with 2 successful urls (after timeout/before timeout)', async () => { 72 | const provider100 = new MockJsonRpcProvider(true, 100) as unknown as JsonRpcProvider 73 | const provider1 = new MockJsonRpcProvider(true, 1) as unknown as JsonRpcProvider 74 | 75 | const url = await getBestProvider([provider100, provider1], 50) 76 | expect(url === provider1).toBe(true) 77 | }) 78 | }) 79 | -------------------------------------------------------------------------------- /packages/store/src/index.ts: -------------------------------------------------------------------------------- 1 | import { getAddress } from '@ethersproject/address' 2 | import type { Actions, Web3ReactState, Web3ReactStateUpdate, Web3ReactStore } from '@web3-react/types' 3 | import { createStore } from 'zustand' 4 | 5 | /** 6 | * MAX_SAFE_CHAIN_ID is the upper bound limit on what will be accepted for `chainId` 7 | * `MAX_SAFE_CHAIN_ID = floor( ( 2**53 - 39 ) / 2 ) = 4503599627370476` 8 | * 9 | * @see {@link https://github.com/MetaMask/metamask-extension/blob/b6673731e2367e119a5fee9a454dd40bd4968948/shared/constants/network.js#L31} 10 | */ 11 | export const MAX_SAFE_CHAIN_ID = 4503599627370476 12 | 13 | function validateChainId(chainId: number): void { 14 | if (!Number.isInteger(chainId) || chainId <= 0 || chainId > MAX_SAFE_CHAIN_ID) { 15 | throw new Error(`Invalid chainId ${chainId}`) 16 | } 17 | } 18 | 19 | function validateAccount(account: string): string { 20 | return getAddress(account) 21 | } 22 | 23 | const DEFAULT_STATE = { 24 | chainId: undefined, 25 | accounts: undefined, 26 | activating: false, 27 | } 28 | 29 | export function createWeb3ReactStoreAndActions(): [Web3ReactStore, Actions] { 30 | const store = createStore()(() => DEFAULT_STATE) 31 | 32 | // flag for tracking updates so we don't clobber data when cancelling activation 33 | let nullifier = 0 34 | 35 | /** 36 | * Sets activating to true, indicating that an update is in progress. 37 | * 38 | * @returns cancelActivation - A function that cancels the activation by setting activating to false, 39 | * as long as there haven't been any intervening updates. 40 | */ 41 | function startActivation(): () => void { 42 | const nullifierCached = ++nullifier 43 | 44 | store.setState({ ...DEFAULT_STATE, activating: true }) 45 | 46 | // return a function that cancels the activation iff nothing else has happened 47 | return () => { 48 | if (nullifier === nullifierCached) store.setState({ activating: false }) 49 | } 50 | } 51 | 52 | /** 53 | * Used to report a `stateUpdate` which is merged with existing state. The first `stateUpdate` that results in chainId 54 | * and accounts being set will also set activating to false, indicating a successful connection. 55 | * 56 | * @param stateUpdate - The state update to report. 57 | */ 58 | function update(stateUpdate: Web3ReactStateUpdate): void { 59 | // validate chainId statically, independent of existing state 60 | if (stateUpdate.chainId !== undefined) { 61 | validateChainId(stateUpdate.chainId) 62 | } 63 | 64 | // validate accounts statically, independent of existing state 65 | if (stateUpdate.accounts !== undefined) { 66 | for (let i = 0; i < stateUpdate.accounts.length; i++) { 67 | stateUpdate.accounts[i] = validateAccount(stateUpdate.accounts[i]) 68 | } 69 | } 70 | 71 | nullifier++ 72 | 73 | store.setState((existingState): Web3ReactState => { 74 | // determine the next chainId and accounts 75 | const chainId = stateUpdate.chainId ?? existingState.chainId 76 | const accounts = stateUpdate.accounts ?? existingState.accounts 77 | 78 | // ensure that the activating flag is cleared when appropriate 79 | let activating = existingState.activating 80 | if (activating && chainId && accounts) { 81 | activating = false 82 | } 83 | 84 | return { chainId, accounts, activating } 85 | }) 86 | } 87 | 88 | /** 89 | * Resets connector state back to the default state. 90 | */ 91 | function resetState(): void { 92 | nullifier++ 93 | store.setState(DEFAULT_STATE) 94 | } 95 | 96 | return [store, { startActivation, update, resetState }] 97 | } 98 | -------------------------------------------------------------------------------- /packages/walletconnect-v2/src/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @param rpcMap - Map of chainIds to rpc url(s). 3 | * @param timeout - Timeout, in milliseconds, after which to consider network calls failed. 4 | */ 5 | export async function getBestUrlMap( 6 | rpcMap: Record, 7 | timeout: number 8 | ): Promise<{ [chainId: string]: string }> { 9 | return Object.fromEntries( 10 | await Promise.all(Object.entries(rpcMap).map(async ([chainId, map]) => [chainId, await getBestUrl(map, timeout)])) 11 | ) 12 | } 13 | 14 | /** 15 | * @param urls - An array of URLs to try to connect to. 16 | * @param timeout - {@link getBestUrlMap} 17 | */ 18 | async function getBestUrl(urls: string | string[], timeout: number): Promise { 19 | // if we only have 1 url, it's the best! 20 | if (typeof urls === 'string') return urls 21 | if (urls.length === 1) return urls[0] 22 | 23 | const [HttpConnection, JsonRpcProvider] = await Promise.all([ 24 | import('@walletconnect/jsonrpc-http-connection').then(({ HttpConnection }) => HttpConnection), 25 | import('@walletconnect/jsonrpc-provider').then(({ JsonRpcProvider }) => JsonRpcProvider), 26 | ]) 27 | 28 | // the below returns the first url for which there's been a successful call, prioritized by index 29 | return new Promise((resolve) => { 30 | let resolved = false 31 | const successes: { [index: number]: boolean } = {} 32 | 33 | urls.forEach((url, i) => { 34 | const http = new JsonRpcProvider(new HttpConnection(url)) 35 | 36 | // create a promise that resolves on a successful call, and rejects on a failed call or after timeout milliseconds 37 | const promise = new Promise((resolve, reject) => { 38 | http 39 | .request({ method: 'eth_chainId' }) 40 | .then(() => resolve()) 41 | .catch(() => reject()) 42 | 43 | // set a timeout to reject 44 | setTimeout(() => { 45 | reject() 46 | }, timeout) 47 | }) 48 | 49 | void promise 50 | .then(() => true) 51 | .catch(() => false) 52 | .then((success) => { 53 | // if we already resolved, return 54 | if (resolved) return 55 | 56 | // store the result of the call 57 | successes[i] = success 58 | 59 | // if this is the last call and we haven't resolved yet - do so 60 | if (Object.keys(successes).length === urls.length) { 61 | const index = Object.keys(successes).findIndex((j) => successes[Number(j)]) 62 | // no need to set resolved to true, as this is the last promise 63 | return resolve(urls[index === -1 ? 0 : index]) 64 | } 65 | 66 | // otherwise, for each prospective index, check if we can resolve 67 | new Array(urls.length).fill(0).forEach((_, prospectiveIndex) => { 68 | // to resolve, we need to: 69 | // a) have successfully made a call 70 | // b) not be waiting on any other higher-index calls 71 | if ( 72 | successes[prospectiveIndex] && 73 | new Array(prospectiveIndex).fill(0).every((_, j) => successes[j] === false) 74 | ) { 75 | resolved = true 76 | resolve(urls[prospectiveIndex]) 77 | } 78 | }) 79 | }) 80 | }) 81 | }) 82 | } 83 | 84 | /** 85 | * @param chains - An array of chain IDs. 86 | * @param defaultChainId - The chain ID to treat as the default (it will be the first element in the returned array). 87 | */ 88 | export function getChainsWithDefault(chains: number[], defaultChainId: number) { 89 | const idx = chains.indexOf(defaultChainId) 90 | if (idx === -1) { 91 | throw new Error(`Invalid chainId ${defaultChainId}. Make sure to include it in the "chains" array.`) 92 | } 93 | 94 | const ordered = [...chains] 95 | ordered.splice(idx, 1) 96 | return [defaultChainId, ...ordered] 97 | } 98 | -------------------------------------------------------------------------------- /packages/network/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { JsonRpcProvider } from '@ethersproject/providers' 2 | import type { ConnectionInfo } from '@ethersproject/web' 3 | import type { Actions } from '@web3-react/types' 4 | import { Connector } from '@web3-react/types' 5 | 6 | import { getBestProvider } from './utils' 7 | 8 | type url = string | ConnectionInfo 9 | 10 | function isUrl(url: url | JsonRpcProvider): url is url { 11 | return typeof url === 'string' || ('url' in url && !('connection' in url)) 12 | } 13 | 14 | /** 15 | * @param urlMap - A mapping from chainIds to RPC urls. 16 | * @param defaultChainId - The chainId to connect to in activate if one is not provided. 17 | * @param timeout - Timeout, in milliseconds, after which to treat network calls to urls as failed when selecting 18 | * online providers. 19 | */ 20 | export interface NetworkConstructorArgs { 21 | actions: Actions 22 | urlMap: { [chainId: number]: url | url[] | JsonRpcProvider | JsonRpcProvider[] } 23 | defaultChainId?: number 24 | timeout?: number 25 | } 26 | 27 | export class Network extends Connector { 28 | /** {@inheritdoc Connector.provider} */ 29 | public readonly provider: undefined 30 | /** {@inheritdoc Connector.customProvider} */ 31 | public customProvider?: JsonRpcProvider 32 | 33 | private readonly providerCache: Record | undefined> = {} 34 | 35 | private readonly urlMap: Record 36 | private readonly defaultChainId: number 37 | private readonly timeout: number 38 | 39 | constructor({ 40 | actions, 41 | urlMap, 42 | defaultChainId = Number(Object.keys(urlMap)[0]), 43 | timeout = 5000, 44 | }: NetworkConstructorArgs) { 45 | super(actions) 46 | 47 | this.urlMap = Object.keys(urlMap).reduce((accumulator, chainId) => { 48 | const urls = urlMap[Number(chainId)] 49 | 50 | if (Array.isArray(urls)) { 51 | accumulator[Number(chainId)] = urls 52 | } else { 53 | // this ternary just makes typescript happy, since it can't infer that the array has elements of the same type 54 | accumulator[Number(chainId)] = isUrl(urls) ? [urls] : [urls] 55 | } 56 | 57 | return accumulator 58 | }, {}) 59 | this.defaultChainId = defaultChainId 60 | this.timeout = timeout 61 | } 62 | 63 | private async isomorphicInitialize(chainId: number): Promise { 64 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 65 | if (this.providerCache[chainId]) return this.providerCache[chainId]! 66 | 67 | const urls = this.urlMap[chainId] 68 | 69 | // early return if we have a single jsonrpc provider already 70 | if (urls.length === 1 && !isUrl(urls[0])) { 71 | return (this.providerCache[chainId] = Promise.resolve(urls[0])) 72 | } 73 | 74 | return (this.providerCache[chainId] = import('@ethersproject/providers').then(({ JsonRpcProvider }) => { 75 | const providers = urls.map((url) => (isUrl(url) ? new JsonRpcProvider(url, chainId) : url)) 76 | return getBestProvider(providers, this.timeout) 77 | })) 78 | } 79 | 80 | /** 81 | * Initiates a connection. 82 | * 83 | * @param desiredChainId - The desired chain to connect to. 84 | */ 85 | public async activate(desiredChainId = this.defaultChainId): Promise { 86 | let cancelActivation: () => void 87 | if (!this.providerCache[desiredChainId]) { 88 | cancelActivation = this.actions.startActivation() 89 | } 90 | 91 | return this.isomorphicInitialize(desiredChainId) 92 | .then(async (customProvider) => { 93 | this.customProvider = customProvider 94 | 95 | const { chainId } = await this.customProvider.getNetwork() 96 | this.actions.update({ chainId, accounts: [] }) 97 | }) 98 | .catch((error: Error) => { 99 | cancelActivation?.() 100 | throw error 101 | }) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /packages/gnosis-safe/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { SafeAppProvider } from '@safe-global/safe-apps-provider' 2 | import type SafeAppsSDK from '@safe-global/safe-apps-sdk' 3 | import type { Opts } from '@safe-global/safe-apps-sdk' 4 | import type { Actions } from '@web3-react/types' 5 | import { Connector } from '@web3-react/types' 6 | 7 | export class NoSafeContext extends Error { 8 | public constructor() { 9 | super('The app is loaded outside safe context') 10 | this.name = NoSafeContext.name 11 | Object.setPrototypeOf(this, NoSafeContext.prototype) 12 | } 13 | } 14 | 15 | /** 16 | * @param options - Options to pass to `@safe-global/safe-apps-sdk`. 17 | */ 18 | export interface GnosisSafeConstructorArgs { 19 | actions: Actions 20 | options?: Opts 21 | } 22 | 23 | export class GnosisSafe extends Connector { 24 | /** {@inheritdoc Connector.provider} */ 25 | public provider?: SafeAppProvider 26 | 27 | private readonly options?: Opts 28 | private eagerConnection?: Promise 29 | 30 | /** 31 | * A `SafeAppsSDK` instance. 32 | */ 33 | public sdk: SafeAppsSDK | undefined 34 | 35 | constructor({ actions, options }: GnosisSafeConstructorArgs) { 36 | super(actions) 37 | this.options = options 38 | } 39 | 40 | /** 41 | * A function to determine whether or not this code is executing on a server. 42 | */ 43 | private get serverSide() { 44 | return typeof window === 'undefined' 45 | } 46 | 47 | /** 48 | * A function to determine whether or not this code is executing in an iframe. 49 | */ 50 | private get inIframe() { 51 | if (this.serverSide) return false 52 | if (window !== window.parent) return true 53 | return false 54 | } 55 | 56 | private async isomorphicInitialize(): Promise { 57 | if (this.eagerConnection) return 58 | 59 | // kick off import early to minimize waterfalls 60 | const SafeAppProviderPromise = import('@safe-global/safe-apps-provider').then( 61 | ({ SafeAppProvider }) => SafeAppProvider 62 | ) 63 | 64 | await (this.eagerConnection = import('@safe-global/safe-apps-sdk').then(async (m) => { 65 | this.sdk = new m.default(this.options) 66 | 67 | const safe = await Promise.race([ 68 | this.sdk.safe.getInfo(), 69 | new Promise((resolve) => setTimeout(resolve, 500)), 70 | ]) 71 | 72 | if (safe) { 73 | const SafeAppProvider = await SafeAppProviderPromise 74 | this.provider = new SafeAppProvider(safe, this.sdk) 75 | } 76 | })) 77 | } 78 | 79 | /** {@inheritdoc Connector.connectEagerly} */ 80 | public async connectEagerly(): Promise { 81 | if (!this.inIframe) return 82 | 83 | const cancelActivation = this.actions.startActivation() 84 | 85 | try { 86 | await this.isomorphicInitialize() 87 | if (!this.provider) throw new NoSafeContext() 88 | 89 | this.actions.update({ 90 | chainId: this.provider.chainId, 91 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 92 | accounts: [await this.sdk!.safe.getInfo().then(({ safeAddress }) => safeAddress)], 93 | }) 94 | } catch (error) { 95 | cancelActivation() 96 | throw error 97 | } 98 | } 99 | 100 | public async activate(): Promise { 101 | if (!this.inIframe) throw new NoSafeContext() 102 | 103 | // only show activation if this is a first-time connection 104 | let cancelActivation: () => void 105 | if (!this.sdk) cancelActivation = this.actions.startActivation() 106 | 107 | return this.isomorphicInitialize() 108 | .then(async () => { 109 | if (!this.provider) throw new NoSafeContext() 110 | 111 | this.actions.update({ 112 | chainId: this.provider.chainId, 113 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 114 | accounts: [await this.sdk!.safe.getInfo().then(({ safeAddress }) => safeAddress)], 115 | }) 116 | }) 117 | .catch((error) => { 118 | cancelActivation?.() 119 | throw error 120 | }) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /packages/walletconnect-v2/src/index.spec.ts: -------------------------------------------------------------------------------- 1 | // Not avaiable in the `node` environment, but required by WalletConnect 2 | global.TextEncoder = jest.fn() 3 | global.TextDecoder = jest.fn() 4 | 5 | // We are not using Web3Modal and it is not available in the `node` environment either 6 | jest.mock('@web3modal/standalone', () => ({ Web3Modal: jest.fn().mockImplementation() })) 7 | 8 | import { createWeb3ReactStoreAndActions } from '@web3-react/store' 9 | import { EthereumProvider } from '@walletconnect/ethereum-provider' 10 | 11 | import { WalletConnect, WalletConnectOptions } from '.' 12 | 13 | const createTestEnvironment = (opts: Omit) => { 14 | const [store, actions] = createWeb3ReactStoreAndActions() 15 | const connector = new WalletConnect({ actions, options: { ...opts, projectId: '' } }) 16 | return {connector, store} 17 | } 18 | 19 | const accounts = ['0x0000000000000000000000000000000000000000'] 20 | const chains = [1, 2, 3] 21 | 22 | describe('WalletConnect', () => { 23 | const wc2RequestMock = jest.fn() 24 | let wc2InitMock: jest.Mock 25 | 26 | beforeEach(() => { 27 | const wc2EnableMock = jest.fn().mockResolvedValue(accounts) 28 | // @ts-ignore 29 | // TypeScript error is expected here. We're mocking a factory `init` method 30 | // to only define a subset of `EthereumProvider` that we use internally 31 | wc2InitMock = jest.spyOn(EthereumProvider, 'init').mockImplementation(async (opts) => ({ 32 | // we read this in `enable` to get current chain 33 | accounts, 34 | chainId: opts.chains[0], 35 | // session is an object when connected, undefined otherwise 36 | get session() { 37 | return wc2EnableMock.mock.calls.length > 0 ? {} : undefined 38 | }, 39 | // methods used in `activate` and `isomorphicInitialize` 40 | enable: wc2EnableMock, 41 | // mock EIP-1193 42 | request: wc2RequestMock, 43 | on() { 44 | return this 45 | }, 46 | removeListener() { 47 | return this 48 | }, 49 | })) 50 | }) 51 | 52 | afterEach(() => { 53 | wc2RequestMock.mockReset() 54 | }) 55 | 56 | describe('#connectEagerly', () => { 57 | test('should fail when no existing session', async () => { 58 | const {connector} = createTestEnvironment({ chains }) 59 | await expect(connector.connectEagerly()).rejects.toThrow() 60 | }) 61 | }) 62 | 63 | describe(`#isomorphicInitialize`, () => { 64 | test('should initialize exactly one provider and return a Promise if pending initialization', async () => { 65 | const {connector, store} = createTestEnvironment({ chains }) 66 | connector.activate() 67 | connector.activate() 68 | expect(wc2InitMock).toHaveBeenCalledTimes(1) 69 | }) 70 | }) 71 | 72 | describe('#activate', () => { 73 | test('should activate default chain', async () => { 74 | const {connector, store} = createTestEnvironment({ chains }) 75 | await connector.activate() 76 | expect(store.getState()).toEqual({ 77 | chainId: chains[0], 78 | accounts, 79 | activating: false, 80 | error: undefined, 81 | }) 82 | }) 83 | 84 | test('should activate passed chain', async () => { 85 | const {connector, store} = createTestEnvironment({ chains }) 86 | await connector.activate(2) 87 | expect(store.getState().chainId).toEqual(2) 88 | }) 89 | 90 | test('should throw an error for invalid chain', async () => { 91 | const {connector} = createTestEnvironment({ chains }) 92 | expect(connector.activate(99)).rejects.toThrow() 93 | }) 94 | 95 | test('should switch chain if already connected', async () => { 96 | const {connector} = createTestEnvironment({ chains }) 97 | await connector.activate() 98 | await connector.activate(2) 99 | expect(wc2RequestMock).toHaveBeenCalledWith({ method: 'wallet_switchEthereumChain', params: [{ chainId: '0x2' }] }) 100 | }) 101 | 102 | test('should not switch chain if already connected', async () => { 103 | const {connector} = createTestEnvironment({ chains }) 104 | await connector.activate(2) 105 | await connector.activate(2) 106 | expect(wc2RequestMock).toBeCalledTimes(0) 107 | }) 108 | }) 109 | }) 110 | -------------------------------------------------------------------------------- /packages/network/src/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { createWeb3ReactStoreAndActions } from '@web3-react/store' 2 | import type { Actions, Web3ReactStore } from '@web3-react/types' 3 | import { Network } from './' 4 | 5 | export class MockJsonRpcProvider { 6 | public chainId?: string 7 | 8 | public getNetwork() { 9 | return Promise.resolve({ chainId: this.chainId === undefined ? undefined : Number.parseInt(this.chainId, 16) }) 10 | } 11 | } 12 | 13 | jest.mock('@ethersproject/providers', () => ({ 14 | JsonRpcProvider: MockJsonRpcProvider, 15 | FallbackProvider: class MockFallbackProvider extends MockJsonRpcProvider {}, 16 | })) 17 | 18 | const chainId = '0x1' 19 | const accounts: string[] = [] 20 | 21 | describe('Network', () => { 22 | let store: Web3ReactStore 23 | let connector: Network 24 | let mockConnector: MockJsonRpcProvider 25 | 26 | describe('single url', () => { 27 | beforeEach(() => { 28 | let actions: Actions 29 | ;[store, actions] = createWeb3ReactStoreAndActions() 30 | connector = new Network({ actions, urlMap: { 1: 'https://mock.url' } }) 31 | }) 32 | 33 | test('is un-initialized', async () => { 34 | expect(store.getState()).toEqual({ 35 | chainId: undefined, 36 | accounts: undefined, 37 | activating: false, 38 | error: undefined, 39 | }) 40 | }) 41 | 42 | describe('#activate', () => { 43 | beforeEach(async () => { 44 | // testing hack to ensure the provider is set 45 | await connector.activate() 46 | mockConnector = connector.customProvider as unknown as MockJsonRpcProvider 47 | mockConnector.chainId = chainId 48 | }) 49 | 50 | test('works', async () => { 51 | await connector.activate() 52 | 53 | expect(store.getState()).toEqual({ 54 | chainId: Number.parseInt(chainId, 16), 55 | accounts, 56 | activating: false, 57 | error: undefined, 58 | }) 59 | }) 60 | }) 61 | }) 62 | 63 | describe('array of urls', () => { 64 | beforeEach(async () => { 65 | let actions: Actions 66 | ;[store, actions] = createWeb3ReactStoreAndActions() 67 | connector = new Network({ 68 | actions, 69 | urlMap: { 1: ['https://1.mock.url', 'https://2.mock.url'] }, 70 | }) 71 | }) 72 | 73 | beforeEach(async () => { 74 | // testing hack to ensure the provider is set 75 | await connector.activate() 76 | mockConnector = connector.customProvider as unknown as MockJsonRpcProvider 77 | mockConnector.chainId = chainId 78 | }) 79 | 80 | test('#activate', async () => { 81 | await connector.activate() 82 | 83 | expect(store.getState()).toEqual({ 84 | chainId: Number.parseInt(chainId, 16), 85 | accounts, 86 | activating: false, 87 | error: undefined, 88 | }) 89 | }) 90 | }) 91 | 92 | describe('multiple chains', () => { 93 | beforeEach(async () => { 94 | let actions: Actions 95 | ;[store, actions] = createWeb3ReactStoreAndActions() 96 | connector = new Network({ 97 | actions, 98 | urlMap: { 1: 'https://mainnet.mock.url', 2: 'https://testnet.mock.url' }, 99 | }) 100 | }) 101 | 102 | describe('#activate', () => { 103 | test('chainId = 1', async () => { 104 | // testing hack to ensure the provider is set 105 | await connector.activate() 106 | mockConnector = connector.customProvider as unknown as MockJsonRpcProvider 107 | mockConnector.chainId = chainId 108 | await connector.activate() 109 | 110 | expect(store.getState()).toEqual({ 111 | chainId: 1, 112 | accounts, 113 | activating: false, 114 | error: undefined, 115 | }) 116 | }) 117 | 118 | test('chainId = 2', async () => { 119 | // testing hack to ensure the provider is set 120 | await connector.activate(2) 121 | mockConnector = connector.customProvider as unknown as MockJsonRpcProvider 122 | mockConnector.chainId = '0x2' 123 | await connector.activate(2) 124 | 125 | expect(store.getState()).toEqual({ 126 | chainId: 2, 127 | accounts, 128 | activating: false, 129 | error: undefined, 130 | }) 131 | }) 132 | }) 133 | }) 134 | }) 135 | -------------------------------------------------------------------------------- /example/chains.ts: -------------------------------------------------------------------------------- 1 | import type { AddEthereumChainParameter } from '@web3-react/types' 2 | 3 | const ETH: AddEthereumChainParameter['nativeCurrency'] = { 4 | name: 'Ether', 5 | symbol: 'ETH', 6 | decimals: 18, 7 | } 8 | 9 | const MATIC: AddEthereumChainParameter['nativeCurrency'] = { 10 | name: 'Matic', 11 | symbol: 'MATIC', 12 | decimals: 18, 13 | } 14 | 15 | const CELO: AddEthereumChainParameter['nativeCurrency'] = { 16 | name: 'Celo', 17 | symbol: 'CELO', 18 | decimals: 18, 19 | } 20 | 21 | interface BasicChainInformation { 22 | urls: string[] 23 | name: string 24 | } 25 | 26 | interface ExtendedChainInformation extends BasicChainInformation { 27 | nativeCurrency: AddEthereumChainParameter['nativeCurrency'] 28 | blockExplorerUrls: AddEthereumChainParameter['blockExplorerUrls'] 29 | } 30 | 31 | function isExtendedChainInformation( 32 | chainInformation: BasicChainInformation | ExtendedChainInformation 33 | ): chainInformation is ExtendedChainInformation { 34 | return !!(chainInformation as ExtendedChainInformation).nativeCurrency 35 | } 36 | 37 | export function getAddChainParameters(chainId: number): AddEthereumChainParameter | number { 38 | const chainInformation = CHAINS[chainId] 39 | if (isExtendedChainInformation(chainInformation)) { 40 | return { 41 | chainId, 42 | chainName: chainInformation.name, 43 | nativeCurrency: chainInformation.nativeCurrency, 44 | rpcUrls: chainInformation.urls, 45 | blockExplorerUrls: chainInformation.blockExplorerUrls, 46 | } 47 | } else { 48 | return chainId 49 | } 50 | } 51 | 52 | const getInfuraUrlFor = (network: string) => 53 | process.env.infuraKey ? `https://${network}.infura.io/v3/${process.env.infuraKey}` : undefined 54 | const getAlchemyUrlFor = (network: string) => 55 | process.env.alchemyKey ? `https://${network}.alchemyapi.io/v2/${process.env.alchemyKey}` : undefined 56 | 57 | type ChainConfig = { [chainId: number]: BasicChainInformation | ExtendedChainInformation } 58 | 59 | export const MAINNET_CHAINS: ChainConfig = { 60 | 1: { 61 | urls: [getInfuraUrlFor('mainnet'), getAlchemyUrlFor('eth-mainnet'), 'https://cloudflare-eth.com'].filter(Boolean), 62 | name: 'Mainnet', 63 | }, 64 | 10: { 65 | urls: [getInfuraUrlFor('optimism-mainnet'), 'https://mainnet.optimism.io'].filter(Boolean), 66 | name: 'Optimism', 67 | nativeCurrency: ETH, 68 | blockExplorerUrls: ['https://optimistic.etherscan.io'], 69 | }, 70 | 42161: { 71 | urls: [getInfuraUrlFor('arbitrum-mainnet'), 'https://arb1.arbitrum.io/rpc'].filter(Boolean), 72 | name: 'Arbitrum One', 73 | nativeCurrency: ETH, 74 | blockExplorerUrls: ['https://arbiscan.io'], 75 | }, 76 | 137: { 77 | urls: [getInfuraUrlFor('polygon-mainnet'), 'https://polygon-rpc.com'].filter(Boolean), 78 | name: 'Polygon Mainnet', 79 | nativeCurrency: MATIC, 80 | blockExplorerUrls: ['https://polygonscan.com'], 81 | }, 82 | 42220: { 83 | urls: ['https://forno.celo.org'], 84 | name: 'Celo', 85 | nativeCurrency: CELO, 86 | blockExplorerUrls: ['https://explorer.celo.org'], 87 | }, 88 | } 89 | 90 | export const TESTNET_CHAINS: ChainConfig = { 91 | 5: { 92 | urls: [getInfuraUrlFor('goerli')].filter(Boolean), 93 | name: 'Görli', 94 | }, 95 | 420: { 96 | urls: [getInfuraUrlFor('optimism-goerli'), 'https://goerli.optimism.io'].filter(Boolean), 97 | name: 'Optimism Goerli', 98 | nativeCurrency: ETH, 99 | blockExplorerUrls: ['https://goerli-explorer.optimism.io'], 100 | }, 101 | 421613: { 102 | urls: [getInfuraUrlFor('arbitrum-goerli'), 'https://goerli-rollup.arbitrum.io/rpc'].filter(Boolean), 103 | name: 'Arbitrum Goerli', 104 | nativeCurrency: ETH, 105 | blockExplorerUrls: ['https://testnet.arbiscan.io'], 106 | }, 107 | 80001: { 108 | urls: [getInfuraUrlFor('polygon-mumbai')].filter(Boolean), 109 | name: 'Polygon Mumbai', 110 | nativeCurrency: MATIC, 111 | blockExplorerUrls: ['https://mumbai.polygonscan.com'], 112 | }, 113 | 44787: { 114 | urls: ['https://alfajores-forno.celo-testnet.org'], 115 | name: 'Celo Alfajores', 116 | nativeCurrency: CELO, 117 | blockExplorerUrls: ['https://alfajores-blockscout.celo-testnet.org'], 118 | }, 119 | } 120 | 121 | export const CHAINS: ChainConfig = { 122 | ...MAINNET_CHAINS, 123 | ...TESTNET_CHAINS, 124 | } 125 | 126 | export const URLS: { [chainId: number]: string[] } = Object.keys(CHAINS).reduce<{ [chainId: number]: string[] }>( 127 | (accumulator, chainId) => { 128 | const validURLs: string[] = CHAINS[Number(chainId)].urls 129 | 130 | if (validURLs.length) { 131 | accumulator[Number(chainId)] = validURLs 132 | } 133 | 134 | return accumulator 135 | }, 136 | {} 137 | ) 138 | -------------------------------------------------------------------------------- /packages/types/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { StoreApi } from 'zustand' 2 | 3 | export interface Web3ReactState { 4 | chainId: number | undefined 5 | accounts: string[] | undefined 6 | activating: boolean 7 | } 8 | 9 | export type Web3ReactStore = StoreApi 10 | 11 | export type Web3ReactStateUpdate = 12 | | { 13 | chainId: number 14 | accounts: string[] 15 | } 16 | | { 17 | chainId: number 18 | accounts?: never 19 | } 20 | | { 21 | chainId?: never 22 | accounts: string[] 23 | } 24 | 25 | export interface Actions { 26 | startActivation: () => () => void 27 | update: (stateUpdate: Web3ReactStateUpdate) => void 28 | resetState: () => void 29 | } 30 | 31 | /** per {@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md#request EIP-1193} */ 32 | export interface RequestArguments { 33 | readonly method: string 34 | readonly params?: readonly unknown[] | object 35 | } 36 | 37 | /** per {@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md#events EIP-1193} */ 38 | export interface Provider { 39 | request(args: RequestArguments): Promise 40 | on(eventName: string | symbol, listener: (...args: any[]) => void): this 41 | removeListener(eventName: string | symbol, listener: (...args: any[]) => void): this 42 | } 43 | 44 | /** per {@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md#connect-1 EIP-1193} */ 45 | export interface ProviderConnectInfo { 46 | readonly chainId: string 47 | } 48 | 49 | /** per {@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md#rpc-errors EIP-1193} */ 50 | export interface ProviderRpcError extends Error { 51 | message: string 52 | code: number 53 | data?: unknown 54 | } 55 | 56 | /** Per {@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3085.md#parameters EIP-3085} */ 57 | export interface AddEthereumChainParameter { 58 | chainId: number 59 | chainName: string 60 | nativeCurrency: { 61 | name: string 62 | symbol: string // 2-6 characters long 63 | decimals: 18 64 | } 65 | rpcUrls: string[] 66 | blockExplorerUrls?: string[] 67 | iconUrls?: string[] // Currently ignored. 68 | } 69 | 70 | /** per {@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-747.md#wallet_watchasset-parameters EIP-747} */ 71 | export interface WatchAssetParameters { 72 | address: string // The address that the token is at. 73 | symbol: string // A ticker symbol or shorthand, up to 5 chars. 74 | decimals: number // The number of decimals in the token 75 | image: string // A string url of the token logo 76 | } 77 | 78 | export abstract class Connector { 79 | /** 80 | * An 81 | * EIP-1193 ({@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md}) and 82 | * EIP-1102 ({@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1102.md}) compliant provider. 83 | * May also comply with EIP-3085 ({@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3085.md}). 84 | * This property must be defined while the connector is active, unless a customProvider is provided. 85 | */ 86 | public provider?: Provider 87 | 88 | /** 89 | * An optional property meant to allow ethers providers to be used directly rather than via the experimental 90 | * 1193 bridge. If desired, this property must be defined while the connector is active, in which case it will 91 | * be preferred over provider. 92 | */ 93 | public customProvider?: unknown 94 | 95 | protected readonly actions: Actions 96 | 97 | /** 98 | * An optional handler which will report errors thrown from event listeners. Any errors caused from 99 | * user-defined behavior will be thrown inline through a Promise. 100 | */ 101 | protected onError?: (error: Error) => void 102 | 103 | /** 104 | * @param actions - Methods bound to a zustand store that tracks the state of the connector. 105 | * @param onError - An optional handler which will report errors thrown from event listeners. 106 | * Actions are used by the connector to report changes in connection status. 107 | */ 108 | constructor(actions: Actions, onError?: (error: Error) => void) { 109 | this.actions = actions 110 | this.onError = onError 111 | } 112 | 113 | /** 114 | * Reset the state of the connector without otherwise interacting with the connection. 115 | */ 116 | public resetState(): Promise | void { 117 | this.actions.resetState() 118 | } 119 | 120 | /** 121 | * Initiate a connection. 122 | */ 123 | public abstract activate(...args: unknown[]): Promise | void 124 | 125 | /** 126 | * Attempt to initiate a connection, failing silently 127 | */ 128 | public connectEagerly?(...args: unknown[]): Promise | void 129 | 130 | /** 131 | * Un-initiate a connection. Only needs to be defined if a connection requires specific logic on disconnect. 132 | */ 133 | public deactivate?(...args: unknown[]): Promise | void 134 | 135 | /** 136 | * Attempt to add an asset per EIP-747. 137 | */ 138 | public watchAsset?(params: WatchAssetParameters): Promise 139 | } 140 | -------------------------------------------------------------------------------- /packages/store/src/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { createWeb3ReactStoreAndActions, MAX_SAFE_CHAIN_ID } from '.' 2 | 3 | describe('#createWeb3ReactStoreAndActions', () => { 4 | test('uninitialized', () => { 5 | const [store] = createWeb3ReactStoreAndActions() 6 | expect(store.getState()).toEqual({ 7 | chainId: undefined, 8 | accounts: undefined, 9 | activating: false, 10 | error: undefined, 11 | }) 12 | }) 13 | 14 | describe('#startActivation', () => { 15 | test('works', () => { 16 | const [store, actions] = createWeb3ReactStoreAndActions() 17 | actions.startActivation() 18 | expect(store.getState()).toEqual({ 19 | chainId: undefined, 20 | accounts: undefined, 21 | activating: true, 22 | error: undefined, 23 | }) 24 | }) 25 | 26 | test('cancellation works', () => { 27 | const [store, actions] = createWeb3ReactStoreAndActions() 28 | const cancelActivation = actions.startActivation() 29 | 30 | cancelActivation() 31 | 32 | expect(store.getState()).toEqual({ 33 | chainId: undefined, 34 | accounts: undefined, 35 | activating: false, 36 | error: undefined, 37 | }) 38 | }) 39 | }) 40 | 41 | describe('#update', () => { 42 | test('throws on bad chainIds', () => { 43 | const [, actions] = createWeb3ReactStoreAndActions() 44 | for (const chainId of [1.1, 0, MAX_SAFE_CHAIN_ID + 1]) { 45 | expect(() => actions.update({ chainId })).toThrow(`Invalid chainId ${chainId}`) 46 | } 47 | }) 48 | 49 | test('throws on bad accounts', () => { 50 | const [, actions] = createWeb3ReactStoreAndActions() 51 | expect(() => actions.update({ accounts: ['0x000000000000000000000000000000000000000'] })).toThrow() 52 | }) 53 | 54 | test('chainId', () => { 55 | const [store, actions] = createWeb3ReactStoreAndActions() 56 | const chainId = 1 57 | actions.update({ chainId }) 58 | expect(store.getState()).toEqual({ 59 | chainId, 60 | accounts: undefined, 61 | activating: false, 62 | error: undefined, 63 | }) 64 | }) 65 | 66 | describe('accounts', () => { 67 | test('empty', () => { 68 | const [store, actions] = createWeb3ReactStoreAndActions() 69 | const accounts: string[] = [] 70 | actions.update({ accounts }) 71 | expect(store.getState()).toEqual({ 72 | chainId: undefined, 73 | accounts, 74 | activating: false, 75 | error: undefined, 76 | }) 77 | }) 78 | 79 | test('single', () => { 80 | const [store, actions] = createWeb3ReactStoreAndActions() 81 | const accounts = ['0x0000000000000000000000000000000000000000'] 82 | actions.update({ accounts }) 83 | expect(store.getState()).toEqual({ 84 | chainId: undefined, 85 | accounts, 86 | activating: false, 87 | error: undefined, 88 | }) 89 | }) 90 | 91 | test('multiple', () => { 92 | const [store, actions] = createWeb3ReactStoreAndActions() 93 | const accounts = ['0x0000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000001'] 94 | actions.update({ accounts }) 95 | expect(store.getState()).toEqual({ 96 | chainId: undefined, 97 | accounts, 98 | activating: false, 99 | error: undefined, 100 | }) 101 | }) 102 | }) 103 | 104 | test('both', () => { 105 | const [store, actions] = createWeb3ReactStoreAndActions() 106 | const chainId = 1 107 | const accounts: string[] = [] 108 | actions.update({ chainId, accounts }) 109 | expect(store.getState()).toEqual({ 110 | chainId, 111 | accounts, 112 | activating: false, 113 | error: undefined, 114 | }) 115 | }) 116 | 117 | test('chainId does not unset activating', () => { 118 | const [store, actions] = createWeb3ReactStoreAndActions() 119 | const chainId = 1 120 | actions.startActivation() 121 | actions.update({ chainId }) 122 | expect(store.getState()).toEqual({ 123 | chainId, 124 | accounts: undefined, 125 | activating: true, 126 | error: undefined, 127 | }) 128 | }) 129 | 130 | test('accounts does not unset activating', () => { 131 | const [store, actions] = createWeb3ReactStoreAndActions() 132 | const accounts: string[] = [] 133 | actions.startActivation() 134 | actions.update({ accounts }) 135 | expect(store.getState()).toEqual({ 136 | chainId: undefined, 137 | accounts, 138 | activating: true, 139 | error: undefined, 140 | }) 141 | }) 142 | 143 | test('unsets activating', () => { 144 | const [store, actions] = createWeb3ReactStoreAndActions() 145 | const chainId = 1 146 | const accounts: string[] = [] 147 | actions.startActivation() 148 | actions.update({ chainId, accounts }) 149 | expect(store.getState()).toEqual({ 150 | chainId, 151 | accounts, 152 | activating: false, 153 | error: undefined, 154 | }) 155 | }) 156 | }) 157 | }) 158 | -------------------------------------------------------------------------------- /example/components/ConnectWithSelect.tsx: -------------------------------------------------------------------------------- 1 | import type { CoinbaseWallet } from '@web3-react/coinbase-wallet' 2 | import type { Web3ReactHooks } from '@web3-react/core' 3 | import { GnosisSafe } from '@web3-react/gnosis-safe' 4 | import type { MetaMask } from '@web3-react/metamask' 5 | import { Network } from '@web3-react/network' 6 | import { WalletConnect } from '@web3-react/walletconnect' 7 | import { WalletConnect as WalletConnectV2 } from '@web3-react/walletconnect-v2' 8 | import { useCallback, useEffect, useState } from 'react' 9 | 10 | import { CHAINS, getAddChainParameters } from '../chains' 11 | 12 | function ChainSelect({ 13 | activeChainId, 14 | switchChain, 15 | chainIds, 16 | }: { 17 | activeChainId: number 18 | switchChain: (chainId: number) => void 19 | chainIds: number[] 20 | }) { 21 | return ( 22 | 41 | ) 42 | } 43 | 44 | export function ConnectWithSelect({ 45 | connector, 46 | activeChainId, 47 | chainIds = Object.keys(CHAINS).map(Number), 48 | isActivating, 49 | isActive, 50 | error, 51 | setError, 52 | }: { 53 | connector: MetaMask | WalletConnect | WalletConnectV2 | CoinbaseWallet | Network | GnosisSafe 54 | activeChainId: ReturnType 55 | chainIds?: ReturnType[] 56 | isActivating: ReturnType 57 | isActive: ReturnType 58 | error: Error | undefined 59 | setError: (error: Error | undefined) => void 60 | }) { 61 | const [desiredChainId, setDesiredChainId] = useState(undefined) 62 | 63 | /** 64 | * When user connects eagerly (`desiredChainId` is undefined) or to the default chain (`desiredChainId` is -1), 65 | * update the `desiredChainId` value so that