This guide covers how to implement the wallet aspect of the Dynamic Widget UI Component but using only our SDK hooks and your own UI implementation.

Prerequisites

Like with all this series of headless guides, “headless” is defined a way to use the Dynamic SDK without the need for Dynamic UI components (i.e. DynamicWidget, DynamicUserProfile).

You still need to add the SDK and set up the Dynamic Context Provider (complete the quickstart if you haven’t done so already, or refer to an example app)

Signup/Login

Fetch available wallets

When using the useDynamicContext hook, you’ll find a method called walletConnectorOptions. This gives you a list of the wallets that the user can currently connect to, and it returns them with the WalletOption type, which also provides helpful methods like isInstalledOnBrowser. Using it also safeguards you against scenarios where you might not have the right Wallet Connectors/Chains enabled.

import { useDynamicContext } from '@dynamic-labs/sdk-react-core';

const { walletConnectorOptions } = useDynamicContext();

const availableWallets = walletConnectorOptions;

When browsing wallets in the Dynamic Widget, you might see labels beside them like “Last Used”, “Multichain” or “Recommended”.

Last used comes from the “dynamic_last_used_wallet” value in localstorage. “Multichain” comes from the chainGroup node in each wallet (Remember to also add the WalletConnectors for each chain). “Recommendeded” from the Recommended Wallets feature.

Display a wallet icon

Use the @dynamic-labs/wallet-book library to display a wallet icon using the exported WalletIcon component. This component takes a walletKey prop, which is the key of the wallet you want to display.

import { WalletIcon } from '@dynamic-labs/wallet-book';

const WalletIconComponent = () => {
  return <WalletIcon walletKey="metamask" />;
};

Connect to a wallet

useSelectWalletOption allows you to prompt the user to connect using a specific wallet (by passing in the key).

import { useSelectWalletOption } from "@dynamic-labs/sdk-react-core";

// component setup etc.

const { selectWalletOption } = useSelectWalletOption();

const connectWithWallet = async (walletKey) => {
  return await selectWalletOption(walletKey)
}

Onramp

Show onramp button and open flow

How: Regular button attached to useFunding hook

Hook/Component: useFunding

Notes: Ensure Banxa is enabled in the Dynamic dashboard

import { useFunding } from "@dynamic-labs/sdk-react-core";

const Onramp = () => {
  const { enabled, openFunding } = useFunding();

  return (
    <div>
      {enabled && (
        <button className="onramp-button" onClick={openFunding}>
          Onramp
        </button>
      )}
    </div>
  );
};

Networks

Display current and available networks

How: Use evmNetworks and getNetwork to fetch, use supportsNetworkSwitching and switchNetwork to switch

Hook/Component: useDynamicContext & getNetwork

Notes: This example is for Evm networks only

import { useDynamicContext, getNetwork } from "@dynamic-labs/sdk-react-core";

const CustomNetworkPicker = () => {
  const [currentNetwork, setCurrentNetwork] = useState(null);
  const { primaryWallet } = useDynamicContext();

  const handleNetworkChange = async (event) => {
    const chainId = parseInt(event.target.value);

    if (primaryWallet?.connector?.supportsNetworkSwitching()) {
      try {
        return await primaryWallet?.connector?.switchNetwork({
          networkChainId: chainId,
        });
      } catch (error) {
        console.error("Error switching network", error);
      }
    }
  };

  useEffect(() => {
    if (!currentNetwork)
      getNetwork(primaryWallet?.connector).then((network) => {
        setCurrentNetwork(network);
      });
  }, [primaryWallet]);

  return (
    <>
      {currentNetwork && (
        <Select defaultValue={currentNetwork} onChange={handleNetworkChange}>
          {primaryWallet?.connector?.evmNetworks?.map((network) => (
            <option key={network.chainId} value={network.chainId}>
              {network.name}
            </option>
          ))}
        </Select>
      )}
    </>
  );
};

Primary Wallet

Primary wallet is the wallet that is defined as the active one of all your connected wallets. All interactions, such as signing or creating transactions, will be made with this wallet. In addition, when leveraging our multiasset functionality, this is the wallet that will be display all available tokens.

Display primary wallet address

How: Fetch the primary wallet info from context

Hook/Component: useDynamicContext

Notes: There can be a moment after logging in when it’s not populated yet

import { useDynamicContext } from "@dynamic-labs/sdk-react-core";

const { primaryWallet } = useDynamicContext();

const copyWalletAddress = () => {
  navigator.clipboard.writeText(primaryWallet?.address);
};

return (
  <button onCLick={() => copyWalletAddress()}>
    <p>...{primaryWallet?.address?.slice(-6)}</p>
  </button>
);

Display primary wallet icon

How: Utilize the Walletbook library

Hook/Component: WalletIcon (not publicly documented yet)

Notes: Requires the primaryWallet.connector object.

import { primaryWallet } from "@dynamic-labs/sdk-react-core";
import { WalletIcon } from "@dynamic-labs/wallet-book";

const WalletIconWrapped = ({ connector }) => {

  return (
    <div className="wallet-icon-container">
      <WalletIcon walletKey={primaryWallet?.connector?.key} />
    </div>
  );
};

Show balance of primary wallet

How: getBalance method from useDynamicContext wallet connector

Hook/Component: https://docs.dynamic.xyz/react-sdk/hooks/usedynamiccontext

Notes: None

import { useDynamicContext } from "@dynamic-labs/sdk-react-core";

const { primaryWallet } = useDynamicContext();

const [balance, setBalance] = useState(null);

useEffect(() => {
  const fetchBalance = async () => {
    if (primaryWallet) {
      const value = await primaryWallet.connector.getBalance();
      setBalance(value);
    }
  };
  fetchBalance();
}, [primaryWallet]);

return <p>{balance}</p>;

Show all tokens and balances

How: Mapping return from usetokenbalances hook

Hook/Component: https://docs.dynamic.xyz/react-sdk/hooks/usetokenbalances

Notes: Multi-asset is only supported on the following networks - Ethereum, Optimism, Polygon, Arbitrum, and Base.

import { useTokenBalances } from "@dynamic-labs/sdk-react-core";

const TokenBalances = () => {
  const tokenBalances = useTokenBalances();

  return (
    <div>
      {tokenBalances.map((token) => (
        <div key={token.address}>
          <p>{token.symbol}</p>
          <p>{token.balance}</p>
        </div>
      ))}
    </div>
  );
};

Detect Wallet Locking

The wallet object has a field called connected which indicates whether the wallet is locked or not, and you can use this to trigger your own workflow to warn the user.

Multi-Wallet

Show all linked wallets and allow unlinking

How: Mapping return from useUserWallets hook and handleUnlinkWallet from useDynamicContext

Hook/Component: https://docs.dynamic.xyz/react-sdk/hooks/useuserwallets & https://docs.dynamic.xyz/react-sdk/hooks/usedynamiccontext

Notes: Doesn’t yet show if a wallet is the primary wallet. Unlinking is also handled here.

import {
  useDynamicContext,
  useUserWallets,
} from "@dynamic-labs/sdk-react-core";

import { Tooltip } from "react-tooltip";

const LinkedWallets = () => {
  const { handleUnlinkWallet } = useDynamicContext();

  const userWallets = useUserWallets();

  return (
    <>
      {userWallets.length > 0 && (
        <div className="profile-wallets-container">
          <h2>Connected Wallets</h2>
          {userWallets.length < 2 && <Tooltip id="unlink-tooltip" />}
          <div className="profile-wallets-inner-container">
            {userWallets.map((wallet) => (
              <Flex key={wallet.address}>
                <p>{wallet.address}</p>
                <Spacer />
                <a
                  data-tooltip-id="unlink-tooltip"
                  data-tooltip-content="Can't unlink your only wallet!"
                >
                  <Button
                    size="xs"
                    onClick={() => handleUnlinkWallet(wallet.id)}
                    disabled={userWallets.length < 2}
                  >
                    Unlink
                  </Button>
                </a>
              </Flex>
            ))}
          </div>
        </div>
      )}
    </>
  );
};

Detect which wallet is primary

How: isPrimary boolean on wallets from useUserWallets or grab it directly from useDynamicContext

Hook/Component: useUserWallets & useDynamicContext

import { useDynamicContext, useUserWallets } from "@dynamic-labs/sdk-react-core";

// Method 1
const { primaryWallet } = useDynamicContext();
console.log(primaryWallet);

// Method 2
const userWallets = useUserWallets();
const primaryWallet = userWallets.find((wallet) => wallet.isPrimary);

How: Pop up our linking modal using a hook (setShowLinkNewWalletModal)

Hook/Component: useDynamicModals

Notes:

  • You will need to also use the DynamicMultiWalletPromptsWidget component
import {
  useDynamicModals,
  DynamicMultiWalletPromptsWidget,
} from "@dynamic-labs/sdk-react-core";

const LinkWallet = ({ text }) => {
  const { setShowLinkNewWalletModal } = useDynamicModals();

  return (
    <>
      <div className="link-wallet-container">
        <Button
          className="profile-button"
          onClick={() => setShowLinkNewWalletModal(true)}
        >
          {text}
        </Button>
      </div>
      <DynamicMultiWalletPromptsWidget />
    </>
  );
};

Switch the primary wallet to another

How: setPrimaryWallet from useDynamicContext

Hook/Component: https://docs.dynamic.xyz/react-sdk/hooks/usedynamiccontext

Notes: None

import { useDynamicContext } from "@dynamic-labs/sdk-react-core";

const { setPrimaryWallet } = useDynamicContext();

const handleChangePrimaryWallet = (wallet) => setPrimaryWallet(wallet.id);

Wallet Interactions

To learn how to send balance, sign messages etc., please check out the Wallet Interaction guides where we split into Read actions and Write actions, and we provide per chain examples for common functionality.