Skip to main content
If the user loses their password, they will lose access to their wallet. There is no way to recover the password or the wallet without it.
Password encryption adds an extra layer of security to embedded wallets by requiring a password to decrypt the user’s key share. This ensures that even if an attacker gains access to the stored key share, they cannot use it without the password.

How it works

When password encryption is enabled, it specifically protects the client-side key share:
  1. The user’s client key share is encrypted with their password.
  2. This encrypted share is sent through an encryption proxy which adds a second layer of encryption before storing it on Dynamic’s servers. This ensures the share is double-encrypted and accessible from any device.
  3. Operations like signing transactions require the password to decrypt this share once per session; after unlocking, the user is not prompted again until the next session.
  4. The password is never sent to Dynamic; decryption happens entirely client-side.
  5. No single party (Dynamic, encryption provider) can access the key share alone.

Requiring password for wallets

You can require all wallets to have a password in the Dynamic Dashboard:
  1. Navigate to Embedded Wallets.
  2. Toggle Require Password to enforce that all wallets must be created with a password.
When Require Password is enabled, a password must be provided when creating a wallet. Attempting to create a wallet without a password will return an error. Once the wallet is created and unlocked, it remains unlocked for the rest of the session—the password is not required for every operation.

Creating a password-protected wallet

Use the createWallet method with a password parameter:
import { useReactiveClient } from '@dynamic-labs/react-hooks';
import { dynamicClient } from '<path to client file>';

const CreateWallet = () => {
  const client = useReactiveClient(dynamicClient);

  const createSecureWallet = async (password: string) => {
    await client.wallets.embedded.createWallet({
      chain: 'Evm',
      password
    });

    console.log('Password-protected wallet created');
  };

  return (
    // Your UI implementation
    <></>
  );
};

Password management

  • Users set their own password during wallet creation
  • They must enter the password to unlock the wallet
  • Important: If the user forgets their password, wallet recovery is not possible without a backup.

Unlocking a wallet

If you use Dynamic’s built-in UI, the user is prompted for their password when needed; you do not need to call unlock yourself. The following applies when you build a custom UI or headless flow. Before performing operations with a password-protected wallet in your own flow, unlock it using the unlockWallet method.
import { useReactiveClient } from '@dynamic-labs/react-hooks';
import { dynamicClient } from '<path to client file>';

const UnlockWallet = () => {
  const client = useReactiveClient(dynamicClient);
  const wallet = client.wallets.userWallets[0];

  const handleUnlock = async (password: string) => {
    await client.wallets.waas.unlockWallet(wallet.id, {
      accountAddress: wallet.address,
      password
    });

    console.log('Wallet unlocked for this session');
  };

  return (
    // Your UI implementation
    <></>
  );
};
Once unlocked, the wallet remains unlocked for the rest of the user session (until logout). The user is only asked for the password once per session. Unlocking one wallet unlocks all wallets associated with the user account. As a result, all wallets for a user must share the same password.

Operations requiring password

Any operation that changes the underlying key shares requires the password to be provided again, even if the wallet is currently unlocked. These operations include:
  • Refreshing shares
  • Resharing (Cloud backup)
  • Delegation
This is because the new shares generated during these processes must be encrypted with the password before they are stored.
Dynamic’s UI does not prompt for the password during these operations. You must implement refresh, reshare (e.g. cloud backup), and delegation headlessly: obtain the password in your own flow and pass it programmatically into the SDK when triggering these operations. Do not rely on Dynamic to show a password field for share-changing operations.

Setting a password on an existing account

If a wallet was created without a password, you can add password protection later using setPassword. This is useful when you want to enable password encryption for users who initially created their wallet without one.
import { useReactiveClient } from '@dynamic-labs/react-hooks';
import { dynamicClient } from '<path to client file>';

const SetPassword = () => {
  const client = useReactiveClient(dynamicClient);
  const wallet = client.wallets.userWallets[0];

  const addPasswordToWallet = async (newPassword: string) => {
    await client.wallets.waas.setPassword(wallet.id, {
      accountAddress: wallet.address,
      password: newPassword
    });

    console.log('Password added to existing wallet');
  };

  return (
    // Your UI implementation
    <></>
  );
};

Migrating existing wallets

Enabling Require Password in the dashboard only enforces password protection for new wallets. Existing wallets created before this setting was enabled remain unprotected. To migrate an existing wallet to password protection:
  1. Check if the wallet is already password-protected using getWalletRecoveryState.
  2. If isPasswordEncrypted is false, prompt the user for a password and set it using setPassword.
import { useReactiveClient } from '@dynamic-labs/react-hooks';
import { dynamicClient } from '<path to client file>';

const MigrateWallet = () => {
  const client = useReactiveClient(dynamicClient);
  const wallet = client.wallets.userWallets[0];

  const migrate = async (newPassword: string) => {
    // 1. Check if already encrypted
    const { isPasswordEncrypted } = await client.wallets.waas.getWalletRecoveryState(wallet.id, {
      accountAddress: wallet.address
    });

    if (isPasswordEncrypted) {
      console.log('Wallet is already password protected');
      return;
    }

    // 2. Set password
    await client.wallets.waas.setPassword(wallet.id, {
      accountAddress: wallet.address,
      password: newPassword
    });

    console.log('Wallet migrated');
  };

  return (
    // Your UI implementation
    <></>
  );
};

Updating the password

Change an existing password using updatePassword. This will update the password for all wallets associated with the user account.
import { useReactiveClient } from '@dynamic-labs/react-hooks';
import { dynamicClient } from '<path to client file>';

const UpdatePassword = () => {
  const client = useReactiveClient(dynamicClient);
  const wallet = client.wallets.userWallets[0];

  const changePassword = async (currentPassword: string, newPassword: string) => {
    await client.wallets.waas.updatePassword(wallet.id, {
      accountAddress: wallet.address,
      existingPassword: currentPassword,
      newPassword
    });

    console.log('Password updated');
  };

  return (
    // Your UI implementation
    <></>
  );
};

Checking wallet lock state

You can check if a wallet is currently locked or ready to use with getWalletRecoveryState. This is more accurate than just checking if a password exists, as it tells you if the wallet is currently unlocked for the session.
import { useReactiveClient } from '@dynamic-labs/react-hooks';
import { dynamicClient } from '<path to client file>';

const CheckState = () => {
  const client = useReactiveClient(dynamicClient);
  const wallet = client.wallets.userWallets[0];

  const checkRecoveryState = async () => {
    const { walletReadyState, isPasswordEncrypted } = await client.wallets.waas.getWalletRecoveryState(wallet.id, {
      accountAddress: wallet.address
    });

    if (walletReadyState === 'encrypted') {
      console.log('Wallet is locked. User needs to enter a password.');
    } else if (walletReadyState === 'ready') {
      console.log('Wallet is unlocked and ready to use.');
    }
  };

  return (
    // Your UI implementation
    <></>
  );
};

Complete example

This example demonstrates how to check the wallet state, unlock it if necessary, and then sign a message.
import { useReactiveClient } from '@dynamic-labs/react-hooks';
import { dynamicClient } from '<path to client file>';

const SignMessage = () => {
  const client = useReactiveClient(dynamicClient);
  const wallet = client.wallets.userWallets[0];

  const signWithPassword = async (message: string, password?: string) => {
    // 1. Check if wallet is locked
    const { walletReadyState } = await client.wallets.waas.getWalletRecoveryState(wallet.id, {
      accountAddress: wallet.address
    });

    if (walletReadyState === 'encrypted') {
      // 2. Unlock if needed
      if (!password) {
        throw new Error('Password required to unlock wallet');
      }

      await client.wallets.waas.unlockWallet(wallet.id, {
        accountAddress: wallet.address,
        password
      });
      console.log('Wallet unlocked');
    }

    // 3. Sign message (wallet is now ready)
    const signature = await client.wallets.signMessage({
      wallet,
      message
    });

    return signature;
  };

  return (
    // Your UI implementation
    <></>
  );
};

Security considerations

Password-protected wallets are only as secure as the password itself. Enforce strong password requirements and educate users about password security.
  • Password strength: Require minimum length and complexity
  • No password recovery: If using user-provided passwords, there’s no way to recover a forgotten password without a backup.
  • Session-based unlock: Wallets remain unlocked for the session, reducing friction while maintaining security