> ## Documentation Index
> Fetch the complete documentation index at: https://www.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](/docs/javascript/building-ui/post-auth-user-setup)) and, if your project uses one, its [password](/docs/javascript/building-ui/embedded-wallet-security).

## 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 `WalletReadyState.READY` once the wallet's key material is available to sign with, or `WalletReadyState.ENCRYPTED` when it's still locked behind a password. Use it to decide whether to prompt for the password (see [Embedded wallet password & security](/docs/javascript/building-ui/embedded-wallet-security)) before letting the user sign or export.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={"system"}
    import {
      getWalletRecoveryState,
      WalletReadyState,
    } 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 === WalletReadyState.READY
          ? 'Ready to use'
          : 'Locked — needs password';
    }
    ```
  </Tab>

  <Tab title="React">
    ```tsx theme={"system"}
    import type { WalletAccount } from '@dynamic-labs-sdk/client';
    import { WalletReadyState } from '@dynamic-labs-sdk/client/waas';
    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 === WalletReadyState.READY
            ? 'Ready to use'
            : 'Locked — needs password'}
        </span>
      );
    }
    ```
  </Tab>
</Tabs>

## 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.

<Tabs>
  <Tab title="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>

  <Tab title="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>
      );
    }
    ```
  </Tab>
</Tabs>

<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](/docs/javascript/building-ui/social-oauth-redirect-handling) for the full flow. On React Native, call `signInWithSocialPopUp` instead: it opens an in-app browser and resolves without leaving your screen.
</Note>

<Note>
  Pass the wallet `password` to the backup call when your project [protects wallets with a password](/docs/javascript/building-ui/embedded-wallet-security) — the backup is encrypted with it.
</Note>

## 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.

<Tabs>
  <Tab title="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>

  <Tab title="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} />
        </>
      );
    }
    ```
  </Tab>
</Tabs>

<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.
</Warning>

## 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 === 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](/docs/javascript/building-ui/embedded-wallet-security) — the password that encrypts the backup
* [Social OAuth sign-in & redirect handling](/docs/javascript/building-ui/social-oauth-redirect-handling) — how to obtain the Google access token with Drive scopes
* [Post-login user setup](/docs/javascript/building-ui/post-auth-user-setup) — prompt for backup right after the wallet is created
