> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Wallet backup & recovery

> Show backup status, back an embedded wallet up to Google Drive, and export the private key as a last-resort escape hatch.

## Prerequisites

Before this page: have an embedded wallet (see [Post-login user setup](https://docs.dynamic.xyz/docs/javascript/building-ui/post-auth-user-setup.md)) and, if your project uses one, its [password](https://docs.dynamic.xyz/docs/javascript/building-ui/embedded-wallet-security.md).

## What you'll build

* **Readiness** — tell the user whether their wallet is ready to use or still locked behind a password.
* **Back up** — save the wallet's key shares to the user's Google Drive.
* **Export** — reveal the raw private key in a secure container, so a user can always self-custody.

## Checking wallet readiness

`getWalletRecoveryState` doesn't tell you whether a Google Drive backup exists — there's no SDK call for that. It returns `{ walletReadyState, isPasswordEncrypted }`: `walletReadyState` is `'ready'` once the wallet's key material is available to sign with, or `'encrypted'` when it's still locked behind a password. Use it to decide whether to prompt for the password (see [Embedded wallet password & security](https://docs.dynamic.xyz/docs/javascript/building-ui/embedded-wallet-security.md)) before letting the user sign or export.

> [tab] TypeScript
>
> ```typescript theme={"system"}
> import { getWalletRecoveryState } from '@dynamic-labs-sdk/client/waas';
> import type { WalletAccount } from '@dynamic-labs-sdk/client';
>
> async function renderWalletStatus(walletAccount: WalletAccount, el: HTMLElement) {
>   const { walletReadyState } = await getWalletRecoveryState({
>     walletAccount,
>   });
>
>   el.textContent =
>     walletReadyState === 'ready'
>       ? 'Ready to use'
>       : 'Locked — needs password';
> }
> ```

> [tab] React
>
> ```tsx theme={"system"}
> import type { WalletAccount } from '@dynamic-labs-sdk/client';
> import { useGetWalletRecoveryState } from '@dynamic-labs-sdk/react-hooks';
>
> function WalletStatus({ walletAccount }: { walletAccount: WalletAccount }) {
>   const { data } = useGetWalletRecoveryState({ walletAccount });
>   if (!data) return null;
>
>   return (
>     <span>
>       {data.walletReadyState === 'ready'
>         ? 'Ready to use'
>         : 'Locked — needs password'}
>     </span>
>   );
> }
> ```

## Backing up to Google Drive

Backup uploads the wallet's key shares to the user's Google Drive, which needs two Drive scopes (`GOOGLE_DRIVE_BACKUP_REQUIRED_SCOPES`) granted on their linked Google account. `getGoogleDriveBackupReadiness` does the pre-flight: `status` is `'ready'` when the scopes are already granted, or `'needs-access'` when they aren't.

You don't assemble the scope list yourself — you request the scopes by sending the user back through Google sign-in. With Google Drive backup enabled in your project settings, `signInWithSocialRedirect` (web) automatically adds the required Drive scopes and forces a fresh consent screen. Once the scopes are granted, `backupWaasKeySharesToGoogleDrive` reuses the stored Google token, so you don't pass an access token yourself.

> [tab] TypeScript
>
> ```typescript theme={"system"}
> import {
>   getGoogleDriveBackupReadiness,
>   signInWithSocialRedirect,
> } from '@dynamic-labs-sdk/client';
> import { backupWaasKeySharesToGoogleDrive } from '@dynamic-labs-sdk/client/waas';
> import type { WalletAccount } from '@dynamic-labs-sdk/client';
>
> async function backUp(walletAccount: WalletAccount, password?: string) {
>   const readiness = await getGoogleDriveBackupReadiness();
>
>   // 'needs-access' means the linked Google account is missing the Drive
>   // scopes. Sending the user through Google sign-in grants them: with Google
>   // Drive backup enabled in your project settings, the SDK requests
>   // GOOGLE_DRIVE_BACKUP_REQUIRED_SCOPES and forces a fresh consent screen.
>   if (readiness.status === 'needs-access') {
>     await signInWithSocialRedirect({
>       provider: 'google',
>       redirectUrl: window.location.href,
>     });
>     return;
>   }
>
>   await backupWaasKeySharesToGoogleDrive({ password, walletAccount });
> }
> ```

> [tab] React
>
> ```tsx theme={"system"}
> import type { WalletAccount } from '@dynamic-labs-sdk/client';
> import {
>   useBackupWaasKeySharesToGoogleDrive,
>   useGetGoogleDriveBackupReadiness,
>   useSignInWithSocialRedirect,
> } from '@dynamic-labs-sdk/react-hooks';
>
> function BackupButton({
>   walletAccount,
>   password,
> }: {
>   walletAccount: WalletAccount;
>   password?: string;
> }) {
>   const { data: readiness } = useGetGoogleDriveBackupReadiness();
>   const { mutateAsync: requestDriveAccess } = useSignInWithSocialRedirect();
>   const { mutateAsync: backup, isPending } =
>     useBackupWaasKeySharesToGoogleDrive();
>
>   const onClick = async () => {
>     // Missing Drive scopes: send the user through Google consent. The SDK
>     // requests the required scopes and forces a fresh consent because Google
>     // Drive backup is enabled in your project settings.
>     if (readiness?.status === 'needs-access') {
>       await requestDriveAccess({
>         provider: 'google',
>         redirectUrl: window.location.href,
>       });
>       return;
>     }
>
>     await backup({ password, walletAccount });
>   };
>
>   return (
>     <button type="button" onClick={onClick} disabled={isPending}>
>       {isPending ? 'Backing up…' : 'Back up to Google Drive'}
>     </button>
>   );
> }
> ```

> [!NOTE]
>
> `signInWithSocialRedirect` sends the user to Google and back, so finish the return with the standard redirect flow (`detectSocialRedirectUrl` + `completeSocialRedirect`) and then trigger the backup again — see [Social OAuth sign-in & redirect handling](https://docs.dynamic.xyz/docs/javascript/building-ui/social-oauth-redirect-handling.md) for the full flow. On React Native, call `signInWithSocialPopUp` instead: it opens an in-app browser and resolves without leaving your screen.

> [!NOTE]
>
> Pass the wallet `password` to the backup call when your project [protects wallets with a password](https://docs.dynamic.xyz/docs/javascript/building-ui/embedded-wallet-security.md) — the backup is encrypted with it.

## Exporting the private key

For users who want to self-custody, `exportWaasPrivateKey` renders the key inside a **secure display container** you provide — an element the SDK controls so the key never touches your own DOM or app state.

> [tab] TypeScript
>
> ```typescript theme={"system"}
> import { exportWaasPrivateKey } from '@dynamic-labs-sdk/client/waas';
> import type { WalletAccount } from '@dynamic-labs-sdk/client';
>
> async function exportKey(walletAccount: WalletAccount, password?: string) {
>   const displayContainer = document.querySelector<HTMLElement>('#key-container')!;
>
>   await exportWaasPrivateKey({
>     displayContainer,
>     password,
>     walletAccount,
>   });
> }
> ```

> [tab] React
>
> ```tsx theme={"system"}
> import { exportWaasPrivateKey } from '@dynamic-labs-sdk/client/waas';
> import type { WalletAccount } from '@dynamic-labs-sdk/client';
> import { useRef } from 'react';
>
> function ExportKey({ walletAccount }: { walletAccount: WalletAccount }) {
>   const containerRef = useRef<HTMLDivElement>(null);
>
>   const onClick = async () => {
>     if (!containerRef.current) return;
>     await exportWaasPrivateKey({
>       displayContainer: containerRef.current,
>       walletAccount,
>     });
>   };
>
>   return (
>     <>
>       <button type="button" onClick={onClick}>Reveal private key</button>
>       <div ref={containerRef} />
>     </>
>   );
> }
> ```

> [!WARNING]
>
> Never log, copy to your own state, or transmit an exported private key. Render it only in the SDK's secure container and warn the user to store it somewhere safe.

## Handling errors

| Situation | What to do |
| - | - |
| `readiness.status === 'needs-access'` | Send the user through Google sign-in to grant the Drive scopes, then retry the backup. |
| `readiness.missingScopes` is empty but `status` is `'needs-access'` | Force a fresh consent — the stored token predates scope tracking and can't be trusted. |
| Wrong password on backup/export | Ask the user to re-enter the wallet password; nothing is exported. |
| `walletReadyState === 'encrypted'` | The wallet needs its password before you can back it up or export it — prompt for it first. |

## See also

* [Embedded wallet password & security](https://docs.dynamic.xyz/docs/javascript/building-ui/embedded-wallet-security.md) — the password that encrypts the backup
* [Social OAuth sign-in & redirect handling](https://docs.dynamic.xyz/docs/javascript/building-ui/social-oauth-redirect-handling.md) — how to obtain the Google access token with Drive scopes
* [Post-login user setup](https://docs.dynamic.xyz/docs/javascript/building-ui/post-auth-user-setup.md) — prompt for backup right after the wallet is created


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.