Skip to content

🧰 A simple, maximally extensible, dependency minimized framework for building modern Ethereum dApps

License

Notifications You must be signed in to change notification settings

niZmosis/web3-react

 
 

Repository files navigation

web3-react (beta)

CI

Looking for the prior version of this library? It's available on the v6 branch.

This is a hosted version of example.

Packages

Package Version Size Link
@web3-react/types npm minzip
@web3-react/store npm minzip
@web3-react/core npm minzip
@web3-react/store-redux npm minzip
@web3-react/core-redux npm minzip
EVM Connectors
@web3-react/eip1193 npm minzip EIP-1193
@web3-react/empty npm minzip
@web3-react/gnosis-safe npm minzip Gnosis Safe
@web3-react/metamask npm minzip MetaMask
@web3-react/network npm minzip
@web3-react/url npm minzip
@web3-react/walletconnect-v2 npm minzip WalletConnect
@web3-react/coinbase-wallet npm minzip Coinbase Wallet
@web3-react/bsc-wallet npm minzip BSC Wallet
@web3-react-trust-wallet npm minzip Trust Wallet
@avalabs/web3-react-core-connector npm minzip Core Wallet
@venly/web3-react-venly npm minzip Venly
Cardano Connectors
@web3-react/nami npm minzip Nami
@web3-react/yoroi npm minzip Yoroi
Solana Connectors
@web3-react/phantom npm minzip Phantom
@web3-react/solflare npm minzip Solflare
Tron Connectors
@web3-react/tronLink npm minzip TronLink

Get Started

  • yarn
  • yarn start

In addition to compiling each package in watch mode, this will also spin up /example on localhost:3000.

Run Tests

  • yarn build
  • yarn test --watch

Publish

  • yarn lerna publish [--dist-tag]

Documentation

This version of web3-react is still in beta, so unfortunately documentation is pretty sparse at the moment. /example, TSDoc comments, and the source code itself are the best ways to get an idea of what's going on. More thorough documentation is a priority as development continues!

Upgrading Connector Dependencies

Some connectors have one or more dependencies that are specific to the connection method in question. For example, the walletconnect connector relies on @walletconnect/ethereum-provider package to handle a lot of the connection logic. Often, you may wish to upgrade to the latest version of a client package, to take advantage of the latest features. web3-react makes the process of upgrading client packages fairly painless by specifying them as peerDependencies. This means that you have to explicitly install client packages, and therefore may transparently switch between any version that agrees with the semver specified in the connector (usually any matching major).

Third-Party Connectors

The decision to publish a connector under the @web3-react namespace is fully up to the discretion of the team. However, third-party connectors are always welcome! This library was designed to be highly modular, and you should be able to draw inspiration from the existing connectors to write your own. That connector can live inside your codebase, or even be published as a standalone package. A selection of third-party connectors that have widespread usage may be featured below, PRs modifying this list are welcome.

Upgrading from v6

While the internals of web3-react have changed fairly dramatically between v6 and v8, the hope is that usage don't have to change too much when upgrading. Once you've migrated to the new connectors and state management patterns, you should be able to use the hooks defined in @web3-react/core, in particular useWeb3React (or usePriorityWeb3React), as more or less drop-in replacements for the v6 hooks. The big benefit in v8 is that hooks are now per-connector, as opposed to global, so no more juggling between connectors/multiple roots!

Migrating from v6 to v8 (with Web3ReactProvider)

Connectors are now setup independently, in which you may get the connector and its hooks directly, meaning you don't have to use a Web3ReactProvider. If you want a to connect a bunch of connectors, and be able to select what connector to use in the useWeb3React hook, the Web3ReactProvider is what you're looking for.

Let's start by upgrading the packages. This example is using MetaMask and Coinbase Wallet connectors, as they have the most features. The @^ will let you choose what package version to install. @web3-react/store and @web3-react/types will be installed as a dependency of @web3-react/core. Zustand will also be installed as a dependency of @web3-react/core, which is used by each connector to keep state. All wallet connectors have new packages so remove all the connectors you have. Keep in mind not all connectors available in v6 are in v8.

You may also take a look through the "example-next" package to see how things are setup.

Updating Packages

// Remove the v6 connectors
yarn remove @web3-react/injected-connector @web3-react/walletlink-connector

// Upgrade to the newest versions
yarn upgrade @web3-react/core@^

// Add the new connectors
yarn add @web3-react/metamask@^
yarn add @web3-react/coinbase-wallet@^

Configuring Connectors

v6

Here is how we used to setup the connectors.

const supportedChainIds = [1, 5, 10, 56, 137, 43114, 42161, 42220]

export const injected = new InjectedConnector({
  supportedChainIds,
})

export const walletlink = new WalletLinkConnector({
  url,
  appName,
  supportedChainIds,
  appLogoUrl,
})

export const connectorsByName = {
  Injected: injected,
  WalletLink: walletlink,
}

v8

These exports will allow you to interact with each connector individually. We will use these connector exports to setup our Web3ReactProvider, so we can select what connector we want our useWeb3React hook to use.

Connector Constructor

{
  // Methods bound to a zustand store that tracks the state of the connector.
  actions: Actions
  // An optional handler which will report errors thrown from event listeners.
  onError?: (error: Error) => void
  // Provider specific options.
  options?: Object
  // Additional way of providing the connector with chain parameters for adding chains.
  // If you provide chain parameters this way, you can pass just the chainId to activate().
  chainParameters?: AddEthereumChainParameters
}

File: metaMask.ts

import { initializeConnector } from '@web3-react/core'
import { MetaMask } from '@web3-react/metamask'

export const [metaMask, hooks] = initializeConnector<MetaMask>(
  (actions) => new MetaMask({ actions, options: { mustBeMetaMask: true } })
)

File: coinbaseWallet.ts

import { initializeConnector } from '@web3-react/core'
import { CoinbaseWallet } from '@web3-react/coinbase-wallet'

export const [coinbaseWallet, hooks] = initializeConnector<CoinbaseWallet>(
  (actions) =>
    new CoinbaseWallet({
      actions,
      options: {
        url,
        appName,
        appLogoUrl,
      },
    })
)

If you want to keep the helper to get a connector by its name like in v6, you can do something like this.

import { metaMask } from './connectors/metaMask'
import { coinbaseWallet } from './connectors/coinbaseWallet'

export const connectorsByName = {
  MetaMask: metaMask,
  CoinbaseWallet: coinbaseWallet,
}

Setting up the Web3ReactProvider

v6

Your apps index should be setup similar to this. We no longer need to inject a library like this as each connector has been setup with their own.

function getLibrary(provider) {
  const library = new providers.Web3Provider(provider)
  library.pollingInterval = 8_000
  return library
}

root.render(
  <StrictMode>
    <Web3ReactProvider getLibrary={getLibrary}>
      <App />
    </Web3ReactProvider>
  </StrictMode>
)

v8

With the new configuration, we pull in the connectors we configured and pass them to the Web3ReactProvider.

The optional defaultSelectedConnector prop will let you choose the default selected wallet by the app. If not passed, the selectedConnector will be determined by finding the first "active" connector in the "connectors" array. If there are no "active" connectors, it will be the first element in the "connectors" array, in which the selectedConnector and the priorityConnector are the same. If you do set the defaultSelectedConnector, it won't matter if that default connector is active or not, it will always be the fallback.

import { Web3ReactProvider } from '@web3-react/core'
import { hooks as metaMaskHooks, metaMask } from './connectors/metaMask'
import { coinbaseWallet, hooks as coinbaseWalletHooks } from './connectors/coinbaseWallet'

const connectors = [
  [metaMask, metaMaskHooks],
  [coinbaseWallet, coinbaseWalletHooks],
]

root.render(
  <StrictMode>
    <Web3ReactProvider connectors={connectors} defaultSelectedConnector={coinbaseWallet}>
      <App />
    </Web3ReactProvider>
  </StrictMode>
)

Switching the selectedConnector

You can select what connector you want the Web3ReactProvider to use with a new prop called "setSelectedConnector". If you don't pass it a connector, it will reset to the defaultSelectedConnector if one was provided, or to the priorityConnector.

import { metaMask } from './connectors/metaMask'

const { setSelectedConnector } = useWeb3React()

return (
  <>
    <button onClick={() => setSelectedConnector(metaMask)}>Select</button>
    <button onClick={() => setSelectedConnector()}>Reset to default</button>
  </>
)

That's it for the connector setup, you're now on v8! 🚀

Let's check out what changed in the useWeb3React() hooks.

Hook Changes

v8 has added and removed some props from the useWeb3React hook. The error prop is no longer around and is now handled per connector.

// v6
const {
  // Still in v8
  connector,
  chainId,
  account,
  active, // Renamed to "isActive"
  library, // Renamed to "provider"

  // Removed: Now per "connector"
  activate,
  deactivate,
  setError,
  error,
} = useWeb3React()

// v8
const {
  // In v8 these helper props are from the selectedConnector of the Web3Provider.
  // These are mapped from the selectedConnector within
  // the Web3Provider using the useSelected*() hooks.
  connector,
  chainId,
  accountIndex, // New
  accounts,
  account,
  isActivating, // New
  isActive, // Formerly "active"
  provider, // Formerly "library"
  ENSNames, // New
  ENSName, // New
  ENSAvatars, // New
  ENSAvatar, // New
  addingChain, // New
  switchingChain, // New
  watchingAsset, // New

  // Used to select the connector to be used by the useWeb3React hook.
  // Passing no param will reset to the defaultSelectedConnector
  // if one was provided, or to the priorityConnector.
  setSelectedConnector,

  hooks: {
    // Notice there is no useSelectedConnector hook,
    // that's because these useSelected*() hooks take in a
    // connector to get the relevant hook from the connector.
    // This is useful if you wanted to get a connectors hooks through
    // the Web3Provider without setting it as the selectedConnector.
    useSelectedStore, // Note: No helper prop above
    useSelectedChainId,
    useSelectedAccountIndex,
    useSelectedAccounts,
    useSelectedIsActivating,
    useSelectedAccount,
    useSelectedIsActive,
    useSelectedProvider,
    useSelectedENSNames,
    useSelectedENSName,
    useSelectedENSAvatars,
    useSelectedENSAvatar,
    useSelectedAddingChain,
    useSelectedSwitchingChain,
    useSelectedWatchingAsset,

    // These hooks are taken from the first "active" connector found
    // in the "connectors" array that you passed into the Web3Provider.
    usePriorityConnector,
    usePriorityStore,
    usePriorityChainId,
    usePriorityAccountIndex,
    usePriorityAccounts,
    usePriorityIsActivating,
    usePriorityAccount,
    usePriorityIsActive,
    usePriorityProvider,
    usePriorityENSNames,
    usePriorityENSName,
    usePriorityENSAvatars,
    usePriorityENSAvatar,
    usePriorityAddingChain,
    usePrioritySwitchingChain,
    usePriorityWatchingAsset,
  },
} = useWeb3React()

Using the useSelected*() hook

import { metaMask } from './connectors/metaMask'

const {
  hooks: {
    useSelectedStore,
    useSelectedChainId,
    useSelectedAccountIndex,
    useSelectedAccounts,
    useSelectedIsActivating,
    useSelectedAccount,
    useSelectedIsActive,
    useSelectedProvider,
    useSelectedENSNames,
    useSelectedENSName,
    useSelectedENSAvatars,
    useSelectedENSAvatar,
    useSelectedAddingChain,
    useSelectedSwitchingChain,
    useSelectedWatchingAsset,
  },
} = useWeb3React()

const chainId = useSelectedChainId(metaMask)

Using the usePriority*() hook

The Priority connector is the first "active" connector found in the "connectors" array you passed into the Web3ReactProvider.

const {
  hooks: {
    usePriorityStore,
    usePriorityChainId,
    usePriorityAccountIndex,
    usePriorityAccounts,
    usePriorityIsActivating,
    usePriorityAccount,
    usePriorityIsActive,
    usePriorityProvider,
    usePriorityENSNames,
    usePriorityENSName,
    usePriorityENSAvatars,
    usePriorityENSAvatar,
    usePriorityAddingChain,
    usePrioritySwitchingChain,
    usePriorityWatchingAsset,
  },
} = useWeb3React()

const chainId = usePriorityChainId()

Hooking to a Connector without Web3ReactProvider

With connectors being independent of each other, we can hook into them directly without using the Web3ReactProvider.

import { hooks } from './connectors/metaMask'

const {
  useAccount,
  useAccounts,
  useChainId,
  useAccountIndex,
  useENSName,
  useENSNames,
  useENSAvatar,
  useENSAvatars,
  useIsActivating,
  useIsActive,
  useProvider,
  useAddingChain,
  useSwitchingChain,
  useWatchingAsset,
} = hooks

const [
  account,
  accounts,
  chainId,
  accountIndex,
  ENSName,
  ENSNames,
  ENSAvatar,
  ENSAvatars,
  isActivating,
  isActive,
  provider,
  addingChain,
  switchingChain,
  watchingAsset,
] = [
  useAccount(), // Derived hook
  useAccounts(), // State hook
  useChainId(), // State hook
  useAccountIndex(), // State hook
  useENSName(), // Augmented hook
  useENSNames(), // Augmented hook
  useENSAvatar(), // Augmented hook
  useENSAvatars(), // Augmented hook
  useIsActivating(), // State hook
  useIsActive(), // Derived hook
  useProvider(), // Augmented hook
  useAddingChain(), // State hook
  useSwitchingChain(), // State hook
  useWatchingAsset(), // State hook
]

You can make exposing per connector hooks easier by putting the above code into a helper function.

import { hooks: metaMaskHooks } from './connectors/metaMask'

const {
    account,
    accounts,
    chainId,
    accountIndex,
    ENSName,
    ENSNames,
    ENSAvatar,
    ENSAvatars,
    isActivating,
    isActive,
    provider,
    addingChain,
    switchingChain,
    watchingAsset
} = getPropsFromConnectorHooks(metaMaskHooks)

// Helper
function getPropsFromConnectorHooks(hooks: Web3ReactHooks) {
    const {
        useAccount,
        useAccounts,
        useChainId,
        useAccountIndex,
        useENSName,
        useENSNames,
        useENSAvatar,
        useENSAvatars,
        useIsActivating,
        useIsActive,
        useProvider,
        useAddingChain,
        useSwitchingChain,
        useWatchingAsset
    } = hooks

    const [
        account,
        accounts,
        chainId,
        accountIndex,
        ENSName,
        ENSNames,
        ENSAvatar,
        ENSAvatars,
        isActivating,
        isActive,
        provider,
        addingChain,
        switchingChain,
        watchingAsset
    ] = [
        useAccount(), // Derived hook
        useAccounts(), // State hook
        useChainId(), // State hook
        useAccountIndex(), // State hook
        useENSName(), // Augmented hook
        useENSNames(), // Augmented hook
        useENSAvatar(), // Augmented hook
        useENSAvatars(), // Augmented hook
        useIsActivating(), // State hook
        useIsActive(), // Derived hook
        useProvider(), // Augmented hook
        useAddingChain(), // State hook
        useSwitchingChain(), // State hook
        useWatchingAsset(), // State hook
    ]

    return {
        account,
        accounts,
        chainId,
        accountIndex,
        ENSName,
        ENSNames,
        ENSAvatar,
        ENSAvatars,
        isActivating,
        isActive,
        provider,
        addingChain,
        switchingChain,
        watchingAsset
    }
}

Using the AddingChain, SwitchingChain, and WatchingAsset Hooks

const {
  hooks: { useSelectedAddingChain, useSelectedSwitchingChain, useSelectedWatchingAsset },
} = useWeb3React()

const [addingChain, switchingChain, watchingAsset] = [
  useSelectedAddingChain(),
  useSelectedSwitchingChain(),
  useSelectedWatchingAsset(),
]

const { chainId } = addingChain ?? {}
const { fromChainId, toChainId } = switchingChain ?? {}
const { address, name, decimals, image } = watchingAsset ?? {}

const isPendingChainAdd = !!addingChain
const isPendingChainSwitch = !!switchingChain
const isPendingAssetWatch = !!watchingAsset

Connectors

How connectors are structured now is a lot different than v6. Let's see what's new.

// Initalize a connection
activate()
// Un-initiate a connection
deactivate()
// Attempt to initiate a connection, failing silently
connectEagerly()
// Attempt to add an asset per EIP-747
watchAsset()
// Reset the state of the connector without otherwise interacting with the connection
resetState()

Activate

Activates the connector to either the default chain of the connector, or you can pass in a chainId (number) to connect to a certain chain. If the chain isn't configured in the connector, the user will be prompted to add the chain, given that you passed in the chains parameters to activate()

// Base activation, will connect to the default chain of the connector.
connector.activate()

// Will attempt to activate the connector on the given chain.
// If the chain isn't configured in the connector (eg: MetaMask wallet), it will go to the default.
// If the connector is already active, it will still change the chain.
connector.activate(137)

// You may also provide the chains configuration instead of the chainId.
// This will allow the connector to add the chain if the connector isn't configured for the given chain.
// If the chain already exists, it will simply change to the chain.
// If you passed "chainParameters" to the connectors constructor, you may pass only chainId, as above.
const polygonConfiguration: AddEthereumChainParameter = {
    chainId: 137;
    chainName: 'Polygon';
    nativeCurrency: {
        name: 'Matic';
        symbol: 'MATIC';
        decimals: 18;
    };
    rpcUrls: [
      'https://polygon-rpc.com/',
      'https://rpc-mainnet.matic.network/',
      'https://rpc-mainnet.maticvigil.com/',
      'https://rpc-mainnet.matic.quiknode.pro/',
    ],
    blockExplorerUrls: ['https://polygonscan.com/'],
    iconUrls: [
      'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/info/logo.png'
    ],
}

connector.activate(polygonConfiguration)

Connect Eagerly

Connecting eagerly is very similar to activate() except it won't throw any errors, since it knows it may not connect. It doesn't take in any params.

// Will attempt connect to default chain of the connector.
connector.connectEagerly()

Watch Asset

You can easily add a token (ERC20 compliant) to the connector.

// Coinbase Wallet can take in just the address and try to find the rest
// of the token info, if you so choose to.

const assetToWatch: WatchAssetParameters = {
  // If chainId is provided the connector will switch to the correct chainId before adding the token
  // You may also pass the AddEthereumChainParameter config to the chainId prop incase the user
  // doesn't have the chain configured in their wallet.
  // It will will add the chain, switch to the chain, then add the asset the user wants to watch.
  desiredChainIdOrChainParameters: 137, // Optional
  address: '0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270',
  symbol: 'WMATIC',
  decimals: 18,
  image:
    'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/assets/0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270/logo.png',
}

// Adding WMATIC to the connector on chainId 137.
connector.watchAsset(assetToWatch)

About

🧰 A simple, maximally extensible, dependency minimized framework for building modern Ethereum dApps

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • TypeScript 99.6%
  • Other 0.4%