Skip to main content
No matter how your user signs up, whether it’s through social login such as Telegram, email or phone, or a branded wallet like MetaMask, you can create an embedded wallet for them using Dynamic. Simply choose when you want the wallet to be created and follow the guides below.
Please make sure you are on v4.20.6 before continuing.

During Signup (Automatic)

Creating Embedded Wallets

By default, embedded wallets are created automatically for users during sign-up if they don’t already have a wallet on the enabled chain. All you have to do is check that the “Create on Sign up” toggle is turned on in the Embedded Wallet configuration page.
Automatic embedded wallet creation only creates a single wallet for a user on each chain you have selected. For information on how to create multiple wallets, see the “Creating Wallets Any Time” section below.

Creating Wallets for users with External Wallets

You can automatically create embedded wallets for users who sign in with external wallets like MetaMask. To enable this feature, open the “Create on Sign Up” card, expand the “Advanced Options” panel, and turn on the “Embedded Wallet for Third-Party Wallets” toggle.

Custom Logic (Manual)

Check if User has an Embedded Wallet

You can check if a user has an embedded wallet by using the useDynamicWaas hook.

const { userHasEmbeddedWallet } = useDynamicWaas();

if (userHasEmbeddedWallet) {
  console.log('User has an embedded wallet');
}

Creating Wallets Any Time

If you do not want to create wallets for users automatically when they sign up, you can create wallets for users using custom logic.
To do so, call the createWalletAccount method from the useDynamicWaas hook when you want to create a wallet for a user.
You can control which chains you create wallets on by passing an array of chains to the createWalletAccount method. For Bitcoin wallets, you must provide additional configuration. See the useDynamicWaas documentation for more details.

Basic Example (EVM and Solana)

import { useDynamicWaas, useUserWallets } from "@dynamic-labs/sdk-react-core";
import { ChainEnum } from "@dynamic-labs/sdk-api-core";
// component declaration and all other logic you might need

const { createWalletAccount, getWaasWallets } = useDynamicWaas();

const onCreateWalletHandler = async () => {
  try {
    const waasWallets = await getWaasWallets();
    if (waasWallets.length === 0) {
        await createWalletAccount([ChainEnum.Evm, ChainEnum.Sol]);
    }
  } catch (e) {
    console.error(e);
  }
};

Creating Bitcoin Wallets

When creating Bitcoin (BTC) wallets, you must provide a bitcoinConfig object specifying the address type. The createWalletAccount method supports both a backward-compatible format and a new structured format.
For complete API documentation including all bitcoinConfig properties and options, see the useDynamicWaas hook documentation.
Legacy Format Example:
import { useDynamicWaas } from "@dynamic-labs/sdk-react-core";
import { ChainEnum } from "@dynamic-labs/sdk-api-core";

const { createWalletAccount } = useDynamicWaas();

const handleCreateBTCWallet = async () => {
  try {
    const bitcoinConfig = {
      addressType: 'native_segwit', // or 'taproot'
      network: 'mainnet',
    };
    
    const result = await createWalletAccount(
      [ChainEnum.Btc],
      undefined, // password (optional)
      bitcoinConfig
    );
    
    console.log('Created wallet:', result);
  } catch (error) {
    console.error('Error creating wallet:', error);
  }
};
Structured Format Example (Recommended):
const handleCreateBTCWallet = async () => {
  try {
    const result = await createWalletAccount([
      {
        chain: ChainEnum.Btc,
        bitcoinConfig: {
          addressType: 'taproot',
          network: 'mainnet',
        },
      },
    ]);
    
    console.log('Created wallet:', result);
  } catch (error) {
    console.error('Error creating wallet:', error);
  }
};
Complete Example
Here’s a complete React component example that demonstrates both formats:
import { useState } from 'react';
import { useDynamicWaas } from "@dynamic-labs/sdk-react-core";
import { ChainEnum } from "@dynamic-labs/sdk-api-core";

const CreateWalletAccountExample = () => {
  const [result, setResult] = useState(undefined);
  const [selectedChain, setSelectedChain] = useState(undefined);
  const [selectedAddressType, setSelectedAddressType] = useState(undefined);
  const { createWalletAccount } = useDynamicWaas();

  const isBitcoinSelected = selectedChain === ChainEnum.Btc;

  const handleCreateWallet = async () => {
    if (!selectedChain) {
      setResult('Please select a chain');
      return;
    }

    // Validate address type for BTC
    if (isBitcoinSelected && !selectedAddressType) {
      setResult('Please select an address type for Bitcoin');
      return;
    }

    try {
      // Using backward compatible format
      const bitcoinConfig =
        isBitcoinSelected && selectedAddressType
          ? {
              addressType: selectedAddressType,
              network: 'mainnet',
            }
          : undefined;

      const createWalletAccountResult = await createWalletAccount(
        [selectedChain],
        undefined,
        bitcoinConfig,
      );

      setResult(JSON.stringify(createWalletAccountResult, null, 2));
    } catch (error) {
      setResult(
        `Error: ${error instanceof Error ? error.message : String(error)}`,
      );
    }
  };

  const handleCreateWalletNewFormat = async () => {
    if (!selectedChain) {
      setResult('Please select a chain');
      return;
    }

    if (isBitcoinSelected && !selectedAddressType) {
      setResult('Please select an address type for Bitcoin');
      return;
    }

    try {
      // Using new structured format
      const requirements = [
        {
          chain: selectedChain,
          ...(isBitcoinSelected && selectedAddressType
            ? {
                bitcoinConfig: {
                  addressType: selectedAddressType,
                  network: 'mainnet',
                },
              }
            : {}),
        },
      ];

      const createWalletAccountResult = await createWalletAccount(requirements);

      setResult(JSON.stringify(createWalletAccountResult, null, 2));
    } catch (error) {
      setResult(
        `Error: ${error instanceof Error ? error.message : String(error)}`,
      );
    }
  };

  return (
    <div>
      <select
        value={selectedChain || ''}
        onChange={(e) => {
          const chain = e.target.value;
          setSelectedChain(chain);
          if (chain !== ChainEnum.Btc) {
            setSelectedAddressType(undefined);
          }
        }}
      >
        <option value="">Select a chain</option>
        <option value={ChainEnum.Evm}>EVM</option>
        <option value={ChainEnum.Btc}>Bitcoin</option>
        <option value={ChainEnum.Sol}>Solana</option>
      </select>

      {isBitcoinSelected && (
        <select
          value={selectedAddressType || ''}
          onChange={(e) => setSelectedAddressType(e.target.value)}
        >
          <option value="">Select address type</option>
          <option value="native_segwit">Native SegWit</option>
          <option value="taproot">Taproot</option>
        </select>
      )}

      <button onClick={handleCreateWallet}>
        Create Wallet (Legacy Format)
      </button>
      <button onClick={handleCreateWalletNewFormat}>
        Create Wallet (New Format)
      </button>

      {result && <pre>{result}</pre>}
    </div>
  );
};
Error Handling
Always wrap createWalletAccount calls in try-catch blocks. Common errors include:
  • Missing Bitcoin configuration when creating BTC wallets
  • Invalid or disabled chains
  • Wallet creation failures
For complete error documentation, see the useDynamicWaas API reference.
Best Practices
  1. Validate Bitcoin address type: Ensure you’ve selected an address type before creating a BTC wallet
  2. Use structured format for new code: While both formats work, the structured format is recommended for new implementations
  3. Handle errors gracefully: Always wrap createWalletAccount calls in try-catch blocks
  4. Check enabled chains: Verify that the chains you’re trying to create wallets for are enabled in your project settings

Creating Wallets with a Passcode

You can create MPC wallets that are protected with a passcode for additional security. When a wallet is created with a passcode, certain operations (like signing transactions or exporting keys) will require the passcode to be provided.
import { useDynamicWaas } from "@dynamic-labs/sdk-react-core";
import { ChainEnum } from "@dynamic-labs/sdk-api-core";

const { createWalletAccount } = useDynamicWaas();

const onCreateSecureWalletHandler = async (passcode: string) => {
  try {
    const secureWallet = await createWalletAccount(
      [ChainEnum.Evm],
      passcode
    );
    
    console.log('Secure wallet created:', secureWallet);
  } catch (e) {
    console.error('Error creating secure wallet:', e);
  }
};
For Bitcoin wallets: When creating a Bitcoin wallet with a passcode, you must also provide the bitcoinConfig parameter (legacy format) or include bitcoinConfig in the structured format. See the Creating Bitcoin Wallets section for details on Bitcoin configuration.

Passcode options

There are two approaches to providing the passcode:
ApproachDescriptionBest for
User-providedUser chooses and remembers their own passcodeUsers who want full control over wallet security
Developer-managedYou generate and store the passcodes on your backendSeamless UX without passcode prompts

Why use developer-managed passcodes?

  • Seamless device migration: Users can lose their device and seamlessly migrate to a new one without needing to remember any passcode.
  • Better user experience: No passcode prompts or recovery flows for users to manage.
  • Reduced trust in any single party: No single party (Dynamic, encryption provider, or you) can access the wallet alone—all three would need to collude, eliminating single points of trust.

Important considerations

  • User-provided passcodes: Ensure users understand the importance of backing up their passcodes. If lost, wallet recovery may not be possible.
  • Developer-managed passcodes: Store passcodes securely on your backend with proper encryption. You are responsible for passcode security and availability. Use unique passcodes for each user—do not reuse passcodes across different users.