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

# Recovery

> Recover an account using guardians

## Overview

Social recovery allows users to set one or multiple accounts as *guardians*. Guardians can recover access to the account by approving a change to the validator configuration.

For example, if a user loses access to their key, guardians are able to rotate the signer to a new ECDSA key. Or if a smart account has a multisig configuration, guardians can be used to recover a signer of the multisig or change the threshold.

Guardians can only update the validator configuration, they are not permitted to make any other transactions.

<Warning>Set guardians carefully! They can change the ownership of a smart account without any approval from the previous owner. There is no timelock for the recovery transaction. Prefer setting multiple trusted guardians with a higher signature threshold.</Warning>

<Note>Recovery runs over [ERC-4337](../advanced/erc4337), so it needs a bundler. Guardians can only sign user operations — passing them to `prepareTransaction`, `signMessage`, or `signTypedData` throws, since the recovery module cannot verify signatures on those paths.</Note>

<Note>Recovery produces multiple account calls. Send each one as its own user operation, in the order returned — the recovery module authorizes a single call per user operation, so batching them is rejected onchain.</Note>

## Initialization

To install a social recovery module during account deployment:

```ts {6-8} theme={null}
const rhinestoneAccount = await rhinestone.createAccount({
  owners: {
    type: 'ecdsa',
    accounts: [oldAccount],
  },
  recovery: {
    guardians: [guardianAccount],
  },
})
```

To install the module separately (i.e., when the account is already deployed):

```ts {5} theme={null}
import { enable as enableRecovery } from '@rhinestone/sdk/actions/recovery'

const setUpTransaction = await rhinestoneAccount.sendUserOperation({
  chain,
  calls: [enableRecovery([guardianAccount])],
})
await rhinestoneAccount.waitForExecution(setUpTransaction)
```

### Multiple guardians

You can also set multiple guardians for a single account, and use a custom signature threshold:

```ts {6-9} theme={null}
const rhinestoneAccount = await rhinestone.createAccount({
  owners: {
    type: 'ecdsa',
    accounts: [oldAccount],
  },
  recovery: {
    guardians: [guardianAccountA, guardianAccountB, guardianAccountC],
    threshold: 2,
  },
})
```

Every guardian you pass to `signers` must sign, and you need at least as many as the configured `threshold`.

## Usage

<Tabs>
  <Tab title="ECDSA Account">
    To recover access to the account:

    ```ts {7-10,17-20} theme={null}
    import { recoverEcdsaOwnership } from '@rhinestone/sdk/actions/recovery'

    const recoveryCalls = await recoverEcdsaOwnership({
      accountAddress: rhinestoneAccount.getAddress(),
      chain,
      config: rhinestoneAccount.config,
      newOwners: {
        type: 'ecdsa',
        accounts: [newOwnerAccount],
      },
    })

    for (const call of recoveryCalls) {
      const transaction = await rhinestoneAccount.sendUserOperation({
        chain,
        calls: [call],
        signers: {
          type: 'guardians',
          guardians: [guardianAccountA],
        },
      })
      await rhinestoneAccount.waitForExecution(transaction)
    }
    ```

    This prompts a signature from each guardian account and submits a transaction on their behalf to update the ownership. Existing owners not listed in `newOwners` are removed.
  </Tab>

  <Tab title="Passkey Account">
    To recover access to the account:

    ```ts {6-7,24-27} theme={null}
    import { recoverPasskeyOwnership } from '@rhinestone/sdk/actions/recovery'
    import { parsePublicKey } from '@rhinestone/sdk/signing/passkeys'

    // The account's complete current credential set. The validator stores
    // credentials hashed, so the set cannot be read back onchain.
    const { x, y } = parsePublicKey(passkeyAccountA.publicKey)
    const currentCredentials = [{ pubKeyX: x, pubKeyY: y }]

    const recoveryCalls = await recoverPasskeyOwnership({
      accountAddress: rhinestoneAccount.getAddress(),
      chain,
      config: rhinestoneAccount.config,
      currentCredentials,
      newOwners: {
        type: 'passkey',
        accounts: [passkeyAccountB],
      },
    })

    for (const call of recoveryCalls) {
      const transaction = await rhinestoneAccount.sendUserOperation({
        chain,
        calls: [call],
        signers: {
          type: 'guardians',
          guardians: [guardianAccountA],
        },
      })
      await rhinestoneAccount.waitForExecution(transaction)
    }
    ```

    This prompts a signature from each guardian account and submits a transaction on their behalf to update the ownership.

    <Warning>Pass the account's **complete** current credential set as `currentCredentials`, not just the ones you are replacing. Credentials missing from `newOwners` are removed, and anything already listed is not added again — a partial set makes the recovery re-add an installed credential, which reverts.</Warning>
  </Tab>
</Tabs>

<Note>Ownership is only fully rotated once every call has landed. New owners are added before the old ones are removed, so until the final call executes both remain valid.</Note>

## Nexus accounts

<Info>Nexus accounts must use Ownable V0 as their owner module for social recovery. Pass the same module address in both the account configuration and `newOwners` when recovering the account. Ownable V0 does not support legible EIP-712 signing; typed data and intents remain supported through the SDK's personal-sign fallback.</Info>

```ts {1,9,23} theme={null}
const ownableV0Address =
  '0x2483da3a338895199e5e538530213157e931bf06'

const rhinestoneAccount = await rhinestone.createAccount({
  account: { type: 'nexus' },
  owners: {
    type: 'ecdsa',
    accounts: [oldAccount],
    module: ownableV0Address,
  },
  recovery: {
    guardians: [guardianAccount],
  },
})

const recoveryCalls = await recoverEcdsaOwnership({
  accountAddress: rhinestoneAccount.getAddress(),
  chain,
  config: rhinestoneAccount.config,
  newOwners: {
    type: 'ecdsa',
    accounts: [newOwnerAccount],
    module: ownableV0Address,
  },
})
```

<Warning>Adding `recovery` to an existing Nexus account that uses the default owner validator is not sufficient. The current owner must migrate the account before access is lost. An account whose owner is already unavailable cannot be migrated through guardian recovery.</Warning>

<Warning>This flow does not support EIP-7702 Nexus accounts. Recovery modules cannot revoke the EOA's authority over its delegated account.</Warning>
