Skip to content

peterferguson/rainbowkit

 
 

Repository files navigation

RainbowKit

The best way to connect a wallet 🌈

  • 🔥 Out-of-the-box wallet management
  • ✅ Easily customizable
  • 🦄 Built on top of wagmi and ethers

Installation

Install RainbowKit along with wagmi and its ethers peer dependency.

npm install @rainbow-me/rainbowkit wagmi ethers

Getting started

To start, import RainbowKit’s base styles, configure your wallets and desired chains, generate the required connectors, then wrap your application with RainbowKitProvider and WagmiProvider.

import '@rainbow-me/rainbowkit/styles.css';

import {
  apiProvider,
  configureChains,
  RainbowKitProvider,
  getDefaultWallets,
} from '@rainbow-me/rainbowkit';
import { createClient, chain, WagmiProvider } from 'wagmi';
import { providers } from 'ethers';

const { provider, chains } = configureChains(
  [chain.mainnet],
  [apiProvider.alchemy(process.env.ALCHEMY_ID), apiProvider.fallback()]
);

const { connectors } = getDefaultWallets({
  appName: 'My RainbowKit App',
  chains,
});

const wagmiClient = createClient({
  autoConnect: true,
  connectors,
  provider,
});

const App = () => {
  return (
    <WagmiProvider client={wagmiClient}>
      <RainbowKitProvider chains={chains}>
        <YourApp />
      </RainbowKitProvider>
    </WagmiProvider>
  );
};

Then, in your app, import RainbowKit’s ConnectButton component.

import { ConnectButton } from '@rainbow-me/rainbowkit';

export const YourApp = () => {
  return (
    <>
      <ConnectButton />
    </>
  );
};

You’re done! RainbowKit will now handle your user’s wallet selection, display wallet/transaction information and handle network/wallet switching.

Customizing ConnectButton

The ConnectButton component exposes several props to customize its appearance by toggling the visibility of different elements.

These props can also be defined in a responsive format, e.g. showBalance={{ smallScreen: false, largeScreen: true }}, allowing you to customize its appearance across different screen sizes. Note that the built-in "largeScreen" breakpoint is 768px.

Prop Type Default Description
accountStatus "avatar" | "address" | "full" | { smallScreen: AccountStatus, largeScreen?: AccountStatus } "full" Whether the active account’s avatar and/or address is displayed
chainStatus "icon" | "name" | "full" | "none" | { smallScreen: ChainStatus, largeScreen?: ChainStatus } { smallScreen: "icon", largeScreen: "full" } Whether the current chain’s icon and/or name is displayed, or hidden entirely
showBalance boolean | { smallScreen: boolean, largeScreen?: boolean } { smallScreen: false, largeScreen: true } Whether the balance is visible next to the account name

Choosing a theme

RainbowKit ships with a static CSS file that can be themed via CSS variables, which RainbowKitProvider provides as inline styles by default.

Built-in themes

There are 3 built-in themes:

  • lightTheme (default)
  • darkTheme
  • midnightTheme

These themes are implemented as functions where the resulting theme object can be passed to the theme prop on RainbowKitProvider.

import { RainbowKitProvider, darkTheme } from '@rainbow-me/rainbowkit';

const App = () => {
  return (
    <RainbowKitProvider theme={darkTheme()} {...etc}>
      {/* ... */}
    </RainbowKitProvider>
  );
};

Configuring chains

RainbowKit has built-in Ethereum API provider support so you don't have to worry about defining RPC URLs & a provider instance to pass to wagmi.

Alchemy

To configure the chains with Alchemy configuration, provide apiProvider.alchemy to configureChains.

import { apiProvider, configureChains } from '@rainbow-me/rainbowkit';

...

const { provider, chains } = configureChains(
  [chain.mainnet, chain.polygon],
  [
    apiProvider.alchemy(process.env.ALCHEMY_ID),
    apiProvider.fallback()
  ]
);

Infura

To configure the chains with Infura configuration, provide apiProvider.infura to configureChains.

import { apiProvider, configureChains } from '@rainbow-me/rainbowkit';

...

const { provider, chains } = configureChains(
  [chain.mainnet, chain.polygon],
  [
    apiProvider.infura(process.env.INFURA_ID),
    apiProvider.fallback()
  ]
);

JSON RPC

To configure the chains with your own RPC URLs, provide apiProvider.jsonRpc to configureChains with the chain's RPC URLs.

import { apiProvider } from '@rainbow-me/rainbowkit';

...

const { provider, chains } = configureChains(
  [chain.mainnet, chain.polygon],
  [apiProvider.jsonRpc(chain => ({
    rpcUrl: `https://${chain.id}.example.com`
  }))]
);

Fallback RPC

To configure the chains with their respective fallback (public) RPC URLs, provide apiProvider.fallback to configureChains.

import { apiProvider, configureChains } from '@rainbow-me/rainbowkit';

...

const { provider, chains } = configureChains(
  [chain.mainnet, chain.polygon],
  [apiProvider.fallback()]
);

Note: Only having apiProvider.fallback in your API providers could lead to rate-limiting. It is recommended to also include apiProvider.alchemy, apiProvider.infura or apiProvider.jsonRpc.

Multiple API providers

You can also pass through more than one API provider to configureChains. This is useful if not all your chains support a single API provider. For instance, you may want to use Alchemy for Ethereum, and avax.network for Avalanche.

import { apiProvider, configureChains } from '@rainbow-me/rainbowkit';
import { Chain } from 'wagmi';

...

const avalancheChain: Chain = {
  id: 43_114,
  name: 'Avalanche',
  nativeCurrency: {
    decimals: 18,
    name: 'Avalanche',
    symbol: 'AVAX',
  },
  rpcUrls: {
    default: 'https://api.avax.network/ext/bc/C/rpc',
  },
  blockExplorers: {
    default: { name: 'SnowTrace', url: 'https://snowtrace.io' },
    snowtrace: { name: 'SnowTrace', url: 'https://snowtrace.io' },
  },
  testnet: false,
};

const { provider, chains } = configureChains(
  [chain.mainnet, avalancheChain],
  [
    apiProvider.alchemy(process.env.ALCHEMY_ID),
    apiProvider.jsonRpc(chain => ({ rpcUrl: chain.rpcUrls.default }))
  ]
);

Customizing chains

The chains prop on RainbowKitProvider defines which chains are available for the user to select.

RainbowKit is designed to integrate with wagmi’s chain object which currently provides the following chains:

  • chain.mainnet
  • chain.ropsten
  • chain.rinkeby
  • chain.goerli
  • chain.kovan
  • chain.optimism
  • chain.optimismKovan
  • chain.polygon
  • chain.polygonMumbai
  • chain.arbitrum
  • chain.arbitrumRinkeby
  • chain.localhost
  • chain.hardhat

For more detail about the chain object, or to see examples when creating a custom chain definition, see the source code for wagmi’s chain object.

Your chain config can be defined in a single array provided to configureChains.

import {
  apiProvider,
  configureChains,
  RainbowKitProvider,
  Chain,
} from '@rainbow-me/rainbowkit';
import { chain } from 'wagmi';

const { chains } = configureChains(
  [chain.mainnet, chain.polygon],
  [apiProvider.alchemy(process.env.ALCHEMY_ID), apiProvider.fallback()]
);

const App = () => {
  return (
    <RainbowKitProvider chains={chains} {...etc}>
      {/* ... */}
    </RainbowKitProvider>
  );
};

Several chain icons are provided by default, but you can customize the icon for each chain using the iconUrl property.

const { chains } = configureChains(
  [
    {
      ...chain.mainnet,
      iconUrl: 'https://example.com/icons/ethereum.png',
      iconBackground: 'grey',
    },
    {
      ...chain.polygon,
      iconUrl: 'https://example.com/icons/polygon.png',
      iconBackground: '#7b3fe4',
    },
  ],
  [apiProvider.alchemy(process.env.ALCHEMY_ID), apiProvider.fallback()]
);

Customizing the built-in themes

The built-in theme functions also accept an options object, allowing you to select from several different visual styles.

Option Type Default Description
accentColor string "#0E76FD" The background/text color of various interactive elements.
accentColorForeground string "white" The color used for foreground elements rendered on top of the accent color.
borderRadius "none" | "small" | "medium" | "large" "large" The size of the entire border radius scale
fontStack "rounded" | "system" "rounded" The font stack used throughout the UI. Note that ‘rounded’ attempts to use SF Pro Rounded, falling back to system fonts when it isn’t available.

For example, to customize the dark theme with a purple accent color and a medium border radius scale:

import { RainbowKitProvider, darkTheme } from '@rainbow-me/rainbowkit';

const App = () => {
  return (
    <RainbowKitProvider
      theme={darkTheme({
        accentColor: '#7b3fe4',
        accentColorForeground: 'white',
        borderRadius: 'medium',
      })}
      {...etc}
    >
      {/* ... */}
    </RainbowKitProvider>
  );
};

Each theme also provides several accent color presets (blue, green, orange, pink, purple, red) that can be spread into the options object. For example, to use the pink accent color preset:

import { RainbowKitProvider, darkTheme } from '@rainbow-me/rainbowkit';

const App = () => {
  return (
    <RainbowKitProvider
      theme={darkTheme({
        ...darkTheme.accentColors.pink,
      })}
    >
      {/* Your App */}
    </RainbowKitProvider>
  );
};

Dark mode support

If your app uses the standard prefers-color-mode: dark media query to swap between light and dark modes, you can optionally provide a dynamic theme object containing lightMode and darkMode values.

import {
  RainbowKitProvider,
  lightTheme,
  darkTheme,
} from '@rainbow-me/rainbowkit';

const App = () => {
  return (
    <RainbowKitProvider
      theme={{
        lightMode: lightTheme(),
        darkMode: darkTheme(),
      }}
      {...etc}
    >
      {/* ... */}
    </RainbowKitProvider>
  );
};

Showing recent transactions

You can opt in to displaying recent transactions within RainbowKit’s account modal. Note that all transactions are kept in local storage and must be manually registered with RainbowKit in order to be displayed.

The default ConnectButton implementation will also display a loading indicator around the user’s avatar if there are any pending transactions. Custom ConnectButton implementations can recreate this behavior via the account.hasPendingTransactions property that is passed to your render function.

To use this feature, first enable the showRecentTransactions option on RainbowKitProvider.

import { RainbowKitProvider } from '@rainbow-me/rainbowkit';

const App = () => {
  return (
    <RainbowKitProvider showRecentTransactions={true} {...etc}>
      {/* ... */}
    </RainbowKitProvider>
  );
};

Transactions can then be registered with RainbowKit using the useAddRecentTransaction hook.

import { useAddRecentTransaction } from '@rainbow-me/rainbowkit';

export default () => {
  const addRecentTransaction = useAddRecentTransaction();

  return (
    <button
      onClick={() => {
        addRecentTransaction({
          hash: '0x...',
          description: '...',
        });
      }}
    >
      Add recent transaction
    </button>
  );
};

Once a transaction has been registered with RainbowKit, its status will be updated upon completion.

By default the transaction will be considered completed once a single block has been mined on top of the block in which the transaction was mined, but this can be configured by specifying a custom confirmations value.

import { useAddRecentTransaction } from '@rainbow-me/rainbowkit';

export default () => {
  const addRecentTransaction = useAddRecentTransaction();

  return (
    <button
      onClick={() => {
        addRecentTransaction({
          hash: '0x...',
          description: '...',
          confirmations: 100,
        });
      }}
    >
      Add recent transaction
    </button>
  );
};

Advanced usage

Creating a custom ConnectButton

If you want to create your own custom connection buttons, the low-level ConnectButton.Custom component is also provided which accepts a render prop, i.e. a function as a child. This function is passed everything needed to re-implement the built-in buttons.

A minimal re-implementation of the built-in buttons would look something like this:

import { ConnectButton } from '@rainbow-me/rainbowkit';

export const YourApp = () => {
  return (
    <ConnectButton.Custom>
      {({
        account,
        chain,
        openAccountModal,
        openChainModal,
        openConnectModal,
        mounted,
      }) => {
        return (
          <div
            {...(!mounted && {
              'aria-hidden': true,
              'style': {
                opacity: 0,
                pointerEvents: 'none',
                userSelect: 'none',
              },
            })}
          >
            {(() => {
              if (!mounted || !account || !chain) {
                return (
                  <button onClick={openConnectModal} type="button">
                    Connect Wallet
                  </button>
                );
              }

              if (chain.unsupported) {
                return (
                  <button onClick={openChainModal} type="button">
                    Wrong network
                  </button>
                );
              }

              return (
                <div style={{ display: 'flex', gap: 12 }}>
                  <button
                    onClick={openChainModal}
                    style={{ display: 'flex', alignItems: 'center' }}
                    type="button"
                  >
                    {chain.hasIcon && (
                      <div
                        style={{
                          background: chain.iconBackground,
                          width: 12,
                          height: 12,
                          borderRadius: 999,
                          overflow: 'hidden',
                          marginRight: 4,
                        }}
                      >
                        {chain.iconUrl && (
                          <img
                            alt={chain.name ?? 'Chain icon'}
                            src={chain.iconUrl}
                            style={{ width: 12, height: 12 }}
                          />
                        )}
                      </div>
                    )}
                    {chain.name}
                  </button>

                  <button onClick={openAccountModal} type="button">
                    {account.displayName}
                    {account.displayBalance
                      ? ` (${account.displayBalance})`
                      : ''}
                  </button>
                </div>
              );
            })()}
          </div>
        );
      }}
    </ConnectButton.Custom>
  );
};

The following props are passed to your render function.

Account properties

Property Type Description
account object | undefined Object containing details about the current account, described below
account.address string The full account address, e.g. "0x7a3d05c70581bD345fe117c06e45f9669205384f"
account.balanceDecimals number | undefined The account balance in decimals
account.balanceFormatted string | undefined The account balance formatted as a string, e.g. "1.234567890123456789"
account.balanceSymbol string | undefined The currency symbol for the balance, e.g. "ETH"
account.displayBalance string | undefined The balance formatted to 3 significant digits, plus the symbol, e.g. "1.23 ETH"
account.displayName string The ENS name, or a truncated version of the address, e.g. "rainbowwallet.eth" or "0x7a…384f"
account.ensAvatar string | undefined The ENS avatar URI
account.ensName string | undefined The ENS name, e.g. "rainbowwallet.eth"
account.hasPendingTransactions boolean Boolean indicating whether the account has pending transactions for the current chain

Chain properties

Property Type Description
chain object | undefined Object containing details about the current chain, described below
chain.hasIcon boolean Whether the chain as an icon specified
chain.iconUrl string | undefined The chain icon URL (which may be also be undefined while downloading Base64 data URLs)
chain.iconBackground string | undefined The chain icon background which will be visible while images are loading
chain.id number The chain ID, e.g. 1
chain.name string | undefined The chain name, e.g. "Ethereum"
chain.unsupported boolean | undefined Boolean indicating whether the current chain is unsupported

Modal state properties

Property Type Description
openAccountModal () => void Function to open the account modal
openChainModal () => void Function to open the chain modal
openConnectModal () => void Function to open the connect modal
accountModalOpen boolean Boolean indicating whether the account modal is open
mounted boolean Boolean indicating whether the component has mounted
chainModalOpen boolean Boolean indicating whether the chain modal is open
connectModalOpen boolean Boolean indicating whether the connect modal is open

Creating custom themes

⚠️ Note: This API is unstable and likely to change in the near future. We recommend sticking with the built-in themes for now.

While the built-in themes provide some level of customization, the Theme type is provided to help you define your own custom themes with lower-level access to the underlying theme variables.

import { RainbowKitProvider, Theme } from '@rainbow-me/rainbowkit';

const myCustomTheme: Theme = {
  colors: {
    accentColor: '...',
    accentColorForeground: '...',
    actionButtonBorder: '...',
    actionButtonBorderMobile: '...',
    actionButtonSecondaryBackground: '...',
    closeButton: '...',
    closeButtonBackground: '...',
    connectButtonBackground: '...',
    connectButtonBackgroundError: '...',
    connectButtonInnerBackground: '...',
    connectButtonText: '...',
    connectButtonTextError: '...',
    connectionIndicator: '...',
    error: '...',
    generalBorder: '...',
    generalBorderDim: '...',
    menuItemBackground: '...',
    modalBackdrop: '...',
    modalBackground: '...',
    modalBorder: '...',
    modalText: '...',
    modalTextDim: '...',
    modalTextSecondary: '...',
    profileAction: '...',
    profileActionHover: '...',
    profileForeground: '...',
    selectedOptionBorder: '...',
    standby: '...',
  },
  fonts: {
    body: '...',
  },
  radii: {
    actionButton: '...',
    connectButton: '...',
    menuButton: '...',
    modal: '...',
    modalMobile: '...',
  },
  shadows: {
    connectButton: '...',
    dialog: '...',
    walletLogo: '...',
    profileDetailsAction: '...',
    selectedOption: '...',
    selectedWallet: '...',
  },
};

const App = () => {
  return (
    <RainbowKitProvider theme={myCustomTheme} {...etc}>
      {/* ... */}
    </RainbowKitProvider>
  );
};

Creating custom theme selectors

If your app is server/statically rendered and allows users to manually toggle between themes, RainbowKit’s theming system can be hooked up to custom CSS selectors with the following functions that can be used with any CSS-in-JS system:

  • cssStringFromTheme
  • cssObjectFromTheme

These functions return CSS that sets all required theme variables. Since both strings and objects are supported, this can be integrated with any CSS-in-JS system.

As a basic example, you can render your own style element with custom selectors for each theme. Since we’re taking control of rendering the theme’s CSS, we’re passing null to the theme prop so that RainbowKitProvider doesn’t render any styles for us. Also note the use of the extends option on the cssStringFromTheme function which omits any theme variables that are the same as the base theme.

import {
  RainbowKitProvider,
  cssStringFromTheme,
  lightTheme,
  darkTheme,
} from '@rainbow-me/rainbowkit';

const App = () => {
  return (
    <RainbowKitProvider theme={null} {...etc}>
      <style>
        {`
          :root {
            ${cssStringFromTheme(lightTheme)}
          }

          html[data-dark] {
            ${cssStringFromTheme(darkTheme, {
              extends: lightTheme,
            })}
          }
        `}
      </style>

      {/* ... */}
    </RainbowKitProvider>
  );
};

Customizing the wallet list

⚠️ Note: This API is unstable and likely to change in the near future. We recommend avoiding changes to the wallet list for now.

The following wallet options are presented by default via the getDefaultWallets function:

  • Rainbow
  • WalletConnect
  • Coinbase Wallet
  • MetaMask

An "Injected Wallet" fallback is also provided if window.ethereum exists and hasn’t been provided by another wallet.

All built-in wallets are available via the wallet object which allows you to rearrange/omit wallets as needed.

import { connectorsForWallets, wallet } from '@rainbow-me/rainbowkit';

const needsInjectedWalletFallback =
  typeof window !== 'undefined' &&
  window.ethereum &&
  !window.ethereum.isMetaMask &&
  !window.ethereum.isCoinbaseWallet;

const connectors = connectorsForWallets([
  {
    groupName: 'Suggested',
    wallets: [
      wallet.rainbow({ chains }),
      wallet.walletConnect({ chains }),
      wallet.coinbase({ appName: 'My RainbowKit App', chains }),
      wallet.metaMask({ chains }),
      ...(needsInjectedWalletFallback ? [wallet.injected({ chains })] : []),
    ],
  },
]);

Built-in wallets

The following wallets are provided via the wallet object (in alphabetical order).

Argent

import { wallet } from '@rainbow-me/rainbowkit';

wallet.argent(options: {
  chains: Chain[];
});

Coinbase Wallet

import { wallet } from '@rainbow-me/rainbowkit';

wallet.coinbase(options: {
  appName: string;
  chains: Chain[];
});

Injected Wallet

This is a fallback wallet option designed for scenarios where window.ethereum exists but hasn’t been provided by another wallet in the list.

import { wallet } from '@rainbow-me/rainbowkit';

wallet.injected(options: {
  chains: Chain[];
  shimDisconnect?: boolean;
});

This shouldn’t be used if another injected wallet is available. For example, when combined with MetaMask and Coinbase Wallet:

import { connectorsForWallets, wallet } from '@rainbow-me/rainbowkit';

const needsInjectedWalletFallback =
  typeof window !== 'undefined' &&
  window.ethereum &&
  !window.ethereum.isMetaMask &&
  !window.ethereum.isCoinbaseWallet;

const connectors = connectorsForWallets([
  {
    groupName: 'Suggested',
    wallets: [
      wallet.rainbow({ chains }),
      wallet.walletConnect({ chains }),
      wallet.coinbase({
        chains,
        appName: 'My RainbowKit App',
      }),
      wallet.metaMask({ chains }),
      ...(needsInjectedWalletFallback ? [wallet.injected({ chains })] : []),
    ],
  },
]);

Ledger Live

import { wallet } from '@rainbow-me/rainbowkit';

wallet.ledger(options: {
  chains: Chain[];
  infuraId?: string;
});

MetaMask

import { wallet } from '@rainbow-me/rainbowkit';

wallet.metaMask(options: {
  chains: Chain[];
  shimDisconnect?: boolean;
});

Rainbow

import { wallet } from '@rainbow-me/rainbowkit';

wallet.rainbow(options: {
  chains: Chain[];
});

Trust Wallet

import { wallet } from '@rainbow-me/rainbowkit';

wallet.trust(options: {
  chains: Chain[];
});

WalletConnect

This is a fallback wallet option designed for other WalletConnect-based wallets that haven’t been provided by another wallet in the list.

import { wallet } from '@rainbow-me/rainbowkit';

wallet.walletConnect(options: {
  chains: Chain[];
});

Creating custom wallets

⚠️ Note: This API is unstable and likely to change in the near future. We will be adding more built-in wallets over time, so let us know if there are any particular wallets you’re interested in.

The Wallet type is provided to help you define your own custom wallets. If you’d like to see some working examples, you can view the source code for the built-in wallets.

Wallet properties

Property Type Description
id string Unique ID per wallet
name string Human-readable wallet name
shortName string | undefined Optional short name for mobile use
iconUrl string | (() => Promise<string>) URL for wallet icon, or a promise that resolves to a Base64 data URL (to support bundling lazy-loadable images in JavaScript when publishing to npm)
iconBackground string Background color while the wallet icon loads
installed boolean | undefined Whether the wallet is known to be installed, or undefined if indeterminate
downloadUrls { android?: string, ios?: string, browserExtension?: string, qrCode?: string } | undefined Object containing download URLs
createConnector (connectorArgs: { chainId? number }) => RainbowKitConnector Function for providing the connector instance and configuration for different connection methods, described below

RainbowKitConnector properties

The following properties are defined on the return value of the createConnector function.

Property Type Description
connector Connector Instance of a wagmi connector
desktop { getUri?: () => Promise<string> } | undefined Function for resolving a desktop wallet connection URI
mobile { getUri?: () => Promise<string> } | undefined Function for resolving a mobile wallet connection URI
qrCode { getUri: () => Promise<string>, instructions?: { learnMoreUrl: string, steps: Array<{ step: 'install' | 'create' | 'scan', title: string, description: string }> }}} | undefined Object containing a function for resolving the QR code URI, plus optional setup instructions an an icon URL if different from the wallet icon

Customizing your app’s info

You can pass your app’s info in the appInfo prop for RainbowKitProvider. Properties you can modify are your app's name (appName) and the link where the “Learn More” button in the connection modal redirects to (learnMoreUrl):

import { RainbowKitProvider } from '@rainbow-me/rainbowkit';

const App = () => {
  return (
    <RainbowKitProvider
      appInfo={{
        appName: 'Rainbowkit Demo',
        learnMoreUrl: 'https://learnaboutcryptowallets.example',
      }}
      {...etc}
    >
      {/* ... */}
    </RainbowKitProvider>
  );
};
Property Type Description
learnMoreUrl? string | undefined Introductory “Learn more” link within the “What is a wallet?” button on the connection modal. Defaults to `https://learn.rainbow.me/what-is-a-cryptoweb3-wallet-actually`.
appName? string | undefined Name of your app. Will be displayed in certain places in the RainbowKit UI to refer to your site. Defaults to `undefined`, if left this way we will refer to your site as `"Your App"`.

Enable cool mode

import { RainbowKitProvider } from '@rainbow-me/rainbowkit';

const App = () => {
  return (
    <RainbowKitProvider coolMode={true} {...etc}>
      {/* ... */}
    </RainbowKitProvider>
  );
};

License

MIT.

About

The best way to connect a wallet 🌈 🧰

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • TypeScript 95.0%
  • JavaScript 5.0%