# Migration Challenge

Get a one-time challenge to sign with an existing wallet's private key — consumed by Confirm Client Custody or Register Device Key.

## Overview

Step 3 of the key migration flow: after [Export Encrypted Key](https://docs.aureahub.com/docs/wallets-export-key.md), the device proves it holds the key by signing this challenge. The signature is then submitted to [Confirm Client Custody](https://docs.aureahub.com/docs/wallets-confirm-custody.md) (or [Register Device Key](https://docs.aureahub.com/docs/wallets-register-device.md)) — whichever of the two is called next consumes it.

- The challenge is 32 random bytes, hex-encoded (64 characters), valid for **5 minutes**, and bound to the user and the wallet.
- Sign the 32 bytes the hex decodes to, with the wallet's private key: EIP-191 `personal_sign` for EVM wallets, or an Ed25519 signature sent hex-encoded for Solana wallets. See [Non-Custodial Challenge](https://docs.aureahub.com/docs/wallets-client-challenge.md) for examples.
- Only one unused challenge can exist per wallet. While one exists, this endpoint returns `409`, so request a challenge only when you are ready to sign it, and submit the signature within its 5-minute lifetime.
- Wallets that are already `client_side` are rejected with `409`.

## Endpoint

### `GET /v1/wallets/migration-challenge`

Authentication: bearer token required.

Issues a 32-byte random challenge (TTL 5 min) to sign with the wallet's private key.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `walletId` | string (uuid) | yes | Wallet whose key will sign the challenge. |

**Responses**

`200` OK

```json
{
  "challenge": "b54b30a6f3fbdd6cd84bfdd85aca1b2dc58c9c6b6d6c180e1c7a9bb7bd556f4c",
  "expiresAt": "2026-09-11T10:05:00.000Z"
}
```

`404` Not Found

```json
{
  "statusCode": 404,
  "error": "NotFoundError",
  "message": "Wallet not found"
}
```

**Example request**

```bash
curl "https://api.aureahub.com/v1/wallets/migration-challenge?walletId=3f8b2a1e-5c4d-4e7f-9a0b-1c2d3e4f5a6b" \
  -H "Authorization: Bearer YOUR_TOKEN"
```

## Errors

Error responses

|  |  |  |
| --- | --- | --- |
| 400 | — | Request validation failed — `walletId` missing or not a UUID. |
| 404 | — | `Wallet not found` — unknown id, or the wallet belongs to another user. |
| 409 | — | `Wallet is already fully migrated to client-side custody` |
| 409 | — | `A migration challenge is already pending for this wallet. Sign the existing challenge or wait for it to expire.` |

## Implementation

```javascript
import { getBytes } from 'ethers';

// Fetch and sign in one go — the challenge lives for 5 minutes
async function signMigrationChallenge(accessToken, walletId, walletKey) {
  const res = await fetch(
    `https://api.aureahub.com/v1/wallets/migration-challenge?walletId=${walletId}`,
    { headers: { Authorization: `Bearer ${accessToken}` } }
  );
  if (!res.ok) throw new Error((await res.json()).message);
  const { challenge } = await res.json();

  // EVM: EIP-191 personal_sign over the challenge bytes, with the wallet's key
  return walletKey.signMessage(getBytes('0x' + challenge));
}
```

---

Web version: https://docs.aureahub.com/#wallets-migration-challenge
