Add password protection to embedded wallets for enhanced security
Recommended: JavaScript SDK with React Hooks
For new React apps, we recommend the JavaScript SDK with React Hooks (@dynamic-labs-sdk/react-hooks) instead of the legacy React SDK documented here. The JS SDK comes with many benefits such as a much smaller bundle size and other optimizations. Use the React quickstart (JavaScript SDK) to get started.
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.
When password encryption is enabled, it specifically protects the client-side key share:
The user’s client key share is encrypted with their password.
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.
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.
The password is never sent to Dynamic; decryption happens entirely client-side.
No single party (Dynamic, encryption provider) can access the key share alone.
You can require all wallets to have a password in the Dynamic Dashboard:
Navigate to Embedded Wallets.
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.
If you have enabled Automatic Wallet Creation in the dashboard, Dynamic will automatically prompt the user to create a password when they log in. No additional code is required.
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.
If you are building a custom UI implementation, you must check the wallet lock state and unlock the wallet programmatically before performing any wallet operations. If you skip this step, Dynamic’s default password modal will appear, which defeats the purpose of a custom UI integration.
The required flow for custom UI implementations is:
Check wallet state — Call checkWalletLockState to determine if the wallet is password-encrypted and currently locked.
Unlock if needed — If the wallet is locked, collect the password in your own UI and call unlockWallet.
Proceed with operations — Only after the wallet is unlocked should you perform signing, transactions, or other wallet operations.
import { useWalletPassword } from "@dynamic-labs/sdk-react-core";import { ChainEnum } from "@dynamic-labs/sdk-api-core";const { unlockWallet, checkWalletLockState } = useWalletPassword();const ensureWalletUnlocked = async (accountAddress: string, password: string) => { // 1. Check if wallet is locked const recoveryState = await checkWalletLockState({ accountAddress, chainName: ChainEnum.Evm, }); if (recoveryState?.isPasswordEncrypted) { // 2. Unlock with the user's password const success = await unlockWallet({ accountAddress, chainName: ChainEnum.Evm, password, }); if (!success) { throw new Error('Failed to unlock wallet'); } } // 3. Wallet is now ready for operations};
Before performing operations with a password-protected wallet in your own flow, unlock it using the useWalletPassword hook:
import { useWalletPassword } from "@dynamic-labs/sdk-react-core";import { ChainEnum } from "@dynamic-labs/sdk-api-core";const { unlockWallet, state } = useWalletPassword();const handleUnlock = async (accountAddress: string, password: string) => { const success = await unlockWallet({ accountAddress, chainName: ChainEnum.Evm, password, });if (success) { console.log('Wallet unlocked for this session');}};
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.
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 same password before they are stored.
Dynamic’s UI does not automatically prompt for the password during these operations. You must pass the password programmatically to these functions.
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.
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:
Check if the wallet is already password-protected.
If it is not encrypted, prompt the user for a password and set it.
This example demonstrates how to check the wallet state, unlock it if necessary, and then sign a message — the recommended pattern for custom UI implementations.