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

# Back up a wallet

> This guide will walk you through how to create a backup of a Portal client's wallet.

## Portal-Managed Backups

Portal lets you securely back up your users' MPC wallets so they can recover their wallets even if their device is lost or damaged. By default, Portal encrypts and stores both backup shares ("Portal-Managed Backups"):

1. The **client backup share** is encrypted on the user's device, with the encryption key stored using their chosen backup method (Google Drive, Password, Passkey, or Firebase Auth). The encrypted share is then stored by Portal.
2. The **custodian backup share** is encrypted and stored by Portal, with the encryption key stored in our KMS infrastructure.

<Note>
  By default, Portal manages storing both the encrypted client backup share and the custodian backup share for you. If you prefer to store and manage the backup shares in your own infrastructure instead of using Portal-Managed Backups, see our [Self-Managed Backups](../../../resources/self-managed-backups) guide.
</Note>

Both the client backup share and the custodian backup share are necessary to recover a Portal wallet.

## Backup Methods

You can choose one or more backup methods for storing the encryption key for the client backup share.

### Passkey + Enclave

Your Portal clients can create a passkey to authenticate and manage the private encryption key within a secure enclave.

#### Implementation Requirements

1. Initialize the `Portal` class with a passkey object.
2. Call backup with the Passkey backup method argument.

<Tabs>
  <Tab title="Portal-Managed Backups (Default)">
    ```typescript theme={null}
    import React from 'react'

    const BackupButton: React.FC = () => {
      const handleBackup = async () => {
        // Create a passkey backup of the wallet.
        await portal.backupWallet(BackupMethods.passkey)
      }

      return (
        <button onClick={handleBackup}>Back up your wallet</button>
      )
    }

    export default BackupButton
    ```
  </Tab>

  <Tab title="Self-Managed Backups">
    ```typescript theme={null}
    import axios from 'axios'
    import React from 'react'

    const BackupButton: React.FC = () => {
      const handleBackup = async () => {
        // Get an encrypted user backup share from running backup.
        const { cipherText } = await portal.backupWallet(BackupMethods.passkey)

        try {
          // Send the backup share to your API and store it.
          // This is pseduo code, change it with your URL request to your backend and your expected body data.
          await axios.post('{your_server}/users/[userId]/user-backup-share', {
            data: { backupMethod: "PASSKEY", cipherText }
          })

          // ✅ Notify Portal that the user backup share was stored! 🙌
          await portal.storedClientBackupShare(true, BackupMethods.passkey)
        } catch (error) {
          // ❌ Notify Portal that the user backup share was not stored.
          await portal.storedClientBackupShare(false, BackupMethods.passkey)
        }
      }

      return (
        <button onClick={handleBackup}>Back up your wallet</button>
      )
    }

    export default BackupButton
    ```
  </Tab>
</Tabs>

#### Custom Domain Passkeys

By default, Portal handles passkey operations through our hosted domain (`portalhq.io`). If you want passkeys to be associated with your own domain (e.g., `yourapp.com`), you can configure a custom relying party.

**Benefits of using your own domain:**

* Passkey prompts display your domain name instead of Portal's
* Users see a consistent brand experience
* Passkeys are portable across your applications that share the same relying party

##### Setup Requirements

To use your own domain for passkeys, you'll need to:

1. **Configure DNS** - Point your passkey subdomain (e.g., `passkeys.yourapp.com`) to Portal's infrastructure
2. **Provision a TLS certificate** - Create a certificate for your subdomain that Portal will store in our secure enclave
3. **Configure CORS** - Allowlist your application origins

<Note>
  **Getting Started:** Reach out to the Portal team for instructions on setting up a custom domain, including TLS certificate provisioning for our enclave.
</Note>

##### Configuration

Once your custom domain is set up, configure your passkey options:

```typescript theme={null}
import Portal, { BackupMethods } from '@portal-hq/web'
import { PasskeyOptions } from '@portal-hq/web/types'

const passkeyOptions: PasskeyOptions = {
  customDomain: 'https://passkeys.yourapp.com',  // Your configured subdomain. This will be the same across all environments, even local.
  relyingPartyId: 'yourapp.com',                 // Your root domain. For local dev set this to `localhost`
  relyingPartyName: 'Your App Name',             // Displayed in passkey prompts
  usePopup: false,                               // Direct WebAuthn calls
}

const portal = new Portal({
  apiKey: 'YOUR_CLIENT_API_KEY',
  rpcConfig: {
    'eip155:11155111': 'YOUR_RPC_URL',
  },
})
```

##### Step 1: Create a Passkey

Create a passkey for your user. This can be done separately from the backup flow:

```typescript theme={null}
import React from 'react'

const CreatePasskeyButton: React.FC = () => {
  const handleCreatePasskey = async () => {
    try {
      // Register a passkey without storing an encryption key yet
      await portal.registerPasskey(passkeyOptions)
      console.log('Passkey created successfully')
    } catch (error) {
      console.error('Failed to create passkey:', error)
    }
  }

  return (
    <button onClick={handleCreatePasskey}>Create Passkey</button>
  )
}
```

##### Step 2: Create a Backup

Once a passkey exists, you can create a backup and store the encryption key with it:

<Tabs>
  <Tab title="Portal-Managed Backups">
    ```typescript theme={null}
    import React from 'react'

    const BackupButton: React.FC = () => {
      const handleBackup = async () => {
        try {
          // Step 1: Generate backup share and encryption key
          const { encryptionKey } = await portal.generateBackupShare(
            (status) => console.log('Backup progress:', status)
          )

          // Step 2: Authenticate with passkey and store the encryption key
          await portal.authenticatePasskeyAndWriteKey(encryptionKey, passkeyOptions)

          // Portal stores the cipherText automatically
          console.log('Backup completed successfully')
        } catch (error) {
          console.error('Backup failed:', error)
        }
      }

      return (
        <button onClick={handleBackup}>Back up with Passkey</button>
      )
    }
    ```
  </Tab>

  <Tab title="Self-Managed Backups">
    ```typescript theme={null}
    import axios from 'axios'
    import React from 'react'

    const BackupButton: React.FC = () => {
      const handleBackup = async () => {
        try {
          // Step 1: Generate backup share and encryption key
          const { cipherText, encryptionKey } = await portal.generateBackupShare(
            (status) => console.log('Backup progress:', status)
          )

          // Step 2: Authenticate with passkey and store the encryption key
          await portal.authenticatePasskeyAndWriteKey(encryptionKey, passkeyOptions)

          // Step 3: Store the cipherText in your backend
          // This is pseduo code, change it with your URL request to your backend and your expected body data.
          await axios.post('{your_server}/users/[userId]/user-backup-share', {
            data: { backupMethod: 'CUSTOM', cipherText } // Note this backup method is CUSTOM not PASSKEY (since its decoupled from mpc)
          })

          // Step 4: Signal to Portal that backup storage is complete
          await portal.storedClientBackupShare(true, BackupMethods.custom)

          console.log('Backup completed successfully')
        } catch (error) {
          // Signal failure to Portal
          await portal.storedClientBackupShare(false, BackupMethods.custom)
          console.error('Backup failed:', error)
        }
      }

      return (
        <button onClick={handleBackup}>Back up with Passkey</button>
      )
    }
    ```
  </Tab>
</Tabs>

### Password/PIN

Your Portal clients can create a password/PIN. They can either remember the password or store it in a password storage manager.

#### Implementation Requirements

1. Create a UI for password input.
2. Enforce password requirements. Customer can choose between password, PIN code, passcode, or any other text-based input.
3. If the user forgets their password, there are no additional recovery options.

<Tabs>
  <Tab title="Portal-Managed Backups">
    ```typescript theme={null}
    import React, { FC, useState } from 'react'
    import Portal, { BackupMethods } from '@portal-hq/web'

    const portal = new Portal({
      apiKey: 'YOUR_CLIENT_API_KEY',
      rpcConfig: {
        'eip155:11155111': 'YOUR_RPC_URL',
      },
    })

    const BackupButton: FC = () => {
      const [password, setPassword] = useState<string>('')

      const handleBackup = async () => {
        // Create a password backup for the wallet.
        await portal.backupWallet(BackupMethods.password, undefined, { passwordStorage: { password } })
      }

      return (
        <div>
          <input
            onChange={(e) => setPassword(e.target.value)}
            placeholder="Password/Pin"
            type="password"
            value={password}
          />
          <button onClick={handleBackup}>Back up your wallet</button>
        </div>
      )
    }

    export default BackupButton
    ```
  </Tab>

  <Tab title="Self-Managed Backups">
    ```typescript theme={null}
    import axios from 'axios'
    import React, { FC, useState } from 'react'
    import Portal, { BackupMethods } from '@portal-hq/web'

    const portal = new Portal({
      apiKey: 'YOUR_CLIENT_API_KEY',
      rpcConfig: {
        'eip155:11155111': 'YOUR_RPC_URL',
      },
    })

    const BackupButton: FC = () => {
      const [password, setPassword] = useState<string>('')

      const handleBackup = async () => {
        // Get an encrypted client backup share from running backup.
        const { cipherText } = await portal.backupWallet(BackupMethods.password, undefined, { passwordStorage: { password } })

        try {
          // Send the backup share to your API and store it.
          // This is pseduo code, change it with your URL request to your backend and your expected body data.
          await axios.post('{your_server}/users/[userId]/user-backup-share', {
            data: { backupMethod: "PASSWORD", cipherText }
          })

          // ✅ Notify Portal that the user backup share was stored! 🙌
          await portal.storedClientBackupShare(true, BackupMethods.password)
        } catch (error) {
          // ❌ Notify Portal that the user backup share was not stored.
          await portal.storedClientBackupShare(false, BackupMethods.password)
        }
      }

      return (
        <div>
          <input
            onChange={(e) => setPassword(e.target.value)}
            placeholder="Password/Pin"
            type="password"
            value={password}
          />
          <button onClick={handleBackup}>Back up your wallet</button>
        </div>
      )
    }

    export default BackupButton
    ```
  </Tab>
</Tabs>

### Google Drive

See the docs on how to [set up Google Drive](../../../resources/backup-options/gdrive).

### Firebase Auth Backup

Allow customers to use their existing Firebase Authentication to authenticate into a secure enclave that holds the encryption key for the user. The Portal Web SDK uses Firebase ID tokens to store and retrieve encryption keys from Portal’s token backup service (TBS). This is ideal if your web app already uses Firebase Auth — no additional authentication method is required from your users.

See the [Firebase Auth Backup setup guide](../../../resources/backup-options/firebase-byo-auth) for prerequisites and Firebase project configuration.

#### Implementation requirements

1. Integrate [Firebase Authentication](https://firebase.google.com/docs/auth/web/start) in your web app (for example with the Firebase JavaScript SDK).
2. Call [`portal.configureFirebaseStorage`](/sdks/web/reference#configurefirebasestorage) with a `getToken` callback that returns a Firebase ID token for the signed-in user, or `null` when no user is signed in.
3. Run backup with `BackupMethods.firebase` only after Firebase storage is configured and the user is signed in.

<Note>
  Unlike React Native, the Web SDK does not use a separate `@portal-hq/firebase-storage` package. Firebase backup is built into `@portal-hq/web` via `configureFirebaseStorage`. Your app supplies Firebase Auth; the Portal iframe requests ID tokens from the parent page through a secure `postMessage` bridge.
</Note>

#### Configure Firebase storage

Call `configureFirebaseStorage` before `backupWallet` or `recoverWallet` with `BackupMethods.firebase`. You typically do this immediately before the backup or recovery flow, or once after the user signs in to Firebase.

```typescript theme={null}
import Portal, { BackupMethods } from '@portal-hq/web'
import { getAuth } from 'firebase/auth'

const portal = new Portal({
  apiKey: 'YOUR_PORTAL_CLIENT_API_KEY',
  rpcConfig: {
    'eip155:11155111': 'YOUR_RPC_URL',
  },
})

async function configureFirebaseForPortal() {
  const auth = getAuth()

  portal.configureFirebaseStorage({
    getToken: async (options?: { forceRefresh?: boolean }) => {
      const user = auth.currentUser
      if (!user) {
        return null
      }
      // Portal may call this again with forceRefresh after a 401 — forward it to getIdToken.
      return user.getIdToken(Boolean(options?.forceRefresh))
    },
    // Optional: override TBS host (defaults to backup.web.portalhq.io)
    tbsHost: 'backup.web.portalhq.io',
  })
}
```

<Warning>
  The user must be signed in to Firebase before running backup or recovery. If there is no signed-in user, `getToken` should return `null` and the operation will fail.
</Warning>

The tab examples below assume a `portal` instance and `configureFirebaseForPortal` helper from the [Configure Firebase storage](#configure-firebase-storage) section above.

<Tabs>
  <Tab title="Portal-Managed Backups">
    Ensure the user is signed in to Firebase, configure storage, then run backup. With [Portal-Managed Backups](#portal-managed-backups) (the default), Portal stores the encrypted client backup share for you.

    ```typescript theme={null}
    import React from 'react'
    import { BackupMethods } from '@portal-hq/web'

    const FirebaseBackupButton: React.FC = () => {
      const handleBackup = async () => {
        await configureFirebaseForPortal()

        const { cipherText, storageCallback } = await portal.backupWallet(
          BackupMethods.firebase,
          (status) => {
            console.log('Backup status:', status)
          },
        )

        await storageCallback()
        console.log('Backup cipherText:', cipherText)
      }

      return (
        <button onClick={handleBackup}>Back up with Firebase</button>
      )
    }

    export default FirebaseBackupButton
    ```
  </Tab>

  <Tab title="Self-Managed Backups">
    With self-managed storage, you persist the encrypted client backup share (`cipherText`) in your own backend, then tell Portal the write succeeded or failed. See [Self-Managed Backups](../../../resources/self-managed-backups) for the full model.

    ```typescript theme={null}
    import axios from 'axios'
    import React from 'react'
    import { BackupMethods } from '@portal-hq/web'

    const FirebaseSelfManagedBackupButton: React.FC = () => {
      const handleBackup = async () => {
        await configureFirebaseForPortal()

        const { cipherText, storageCallback } = await portal.backupWallet(
          BackupMethods.firebase,
          (status) => {
            console.log('Backup status:', status)
          },
        )

        try {
          await axios.post('{your_server}/users/[userId]/user-backup-share', {
            data: { backupMethod: 'FIREBASE', cipherText },
          })

          await portal.storedClientBackupShare(true, BackupMethods.firebase)
          await storageCallback()
        } catch (error) {
          await portal.storedClientBackupShare(false, BackupMethods.firebase)
        }
      }

      return (
        <button onClick={handleBackup}>Back up with Firebase</button>
      )
    }

    export default FirebaseSelfManagedBackupButton
    ```
  </Tab>
</Tabs>

**Related documentation**

* [Firebase Auth Backup option](../../../resources/backup-options/firebase-byo-auth)
* [`configureFirebaseStorage` reference](/sdks/web/reference#configurefirebasestorage)
