> For the complete documentation index, see [llms.txt](https://docs.alignedlayer.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.alignedlayer.com/wallet-as-a-service/keys-and-recovery.md).

# Keys and recovery

A passkey lives on one device and cannot be copied off it (unless the platform syncs it, for example through iCloud Keychain). So a user with a phone and a laptop needs a key on each. An account holds up to 10.

Every change to the key set is a transaction from the account itself, signed by an existing key plus Aligned's server key. Nothing is added or removed off chain.

## Adding a device

Two steps, on two devices.

On the **new** device, register a passkey and file a request:

```ts
const request = await aligned.keys.requestWithPasskey({ name: 'Work laptop' });
```

On a device that **already has a key**, approve it:

```ts
const pending = await aligned.keys.listPending();
await aligned.keys.approveWithPasskey(pending[0].id);
```

Approving prompts for the existing passkey and adds the new key on chain. The new device can sign as soon as that lands. `aligned.keys.reject(requestId)` throws the request away instead.

If registration reports `DeviceAlreadyEnrolledError`, this device already holds a passkey for the account. Call `aligned.keys.adoptExistingPasskey()` to use it rather than registering a second one.

## Listing and naming keys

```ts
await aligned.keys.listActive();          // on chain and usable
await aligned.keys.listPending();         // waiting for approval
await aligned.keys.listPendingTimelock(); // added by the guardian, still in the timelock
await aligned.keys.activeOnThisDevice();  // the subset this device can sign with

await aligned.keys.rename(keyId, 'Old phone');
```

Give keys names. A user deciding which device to revoke needs something better than a hash.

## Removing a key

```ts
await aligned.keys.removeWithPasskey(keyHash);
```

The removal is authorized by a key other than the one being removed, so an account can never revoke its last remaining key. The prompt lists the passkeys available on this device, which is how one device revokes another.

## Deciding what to show on startup

On launch, ask whether this device can sign:

```ts
const access = await aligned.keys.ensureDeviceAccess();

switch (access.status) {
  case 'ready':        break;                  // access.activeKey is selected
  case 'no_wallet':    /* offer wallet creation */ break;
  case 'needs_device': /* offer enrollment or recovery */ break;
}
```

It selects a key silently when the device has signed before, and otherwise runs one passkey prompt to pick up a credential the platform synced here or that survived a reinstall. When it still comes up empty, `access.recovery` carries this wallet's recovery terms (whether a guardian exists, and the timelock in days) so the screen can offer the right thing. Call it from a real moment in the UI, not on every render, since it can prompt.

## Guardian recovery

For a user who has lost every device. It only works if the account was created with a guardian and a timelock.

First get a step-up proof, which is a second email code confirming the person is present:

```ts
await aligned.auth.startStepUp();
const { step_up_token } = await aligned.auth.verifyStepUp({ code });
```

Then register a passkey on the new device and start the timelocked add:

```ts
const { valid_at } = await aligned.keys.startRecoveryWithPasskey({
  chainId,
  stepUpToken: step_up_token,
});
```

The key goes on chain immediately but cannot sign until `valid_at`, which is between 1 and 14 days out depending on what the account was created with. Show that date; the wait is the security property, not a delay to work around.

During the wait, any device that still has a working key can throw the recovery away:

```ts
await aligned.keys.cancelRecovery(keyHash);
```

That is the point of the timelock. If someone takes over the account's email and starts a recovery, the real owner has days to stop it.
