# Sign Personal Message

Handle a dApp's `personal_sign` (EIP-191) request. Custodial wallets are signed server-side; wallets without a server-held key get the digest back to sign on the client.

## Overview

The server looks up `walletAddress` among the caller's wallets (case-insensitive) and returns `404` if it is not one of them. `message` is interpreted as hex bytes when it starts with `0x`, and as UTF-8 text otherwise.

- **Server holds the key** (custodial wallets, including wallets whose migration is still `client_side_pending`): the key is decrypted and the response is `{ "signature": "0x…" }` — a 65-byte EIP-191 signature.
- **No server-held key** (`client_side` and MPC `mpc_tss` wallets): nothing is signed and the response is `{ "requiresClientSigning": true, "messageHash": "0x…", "message": "…" }`. See below.
- Only EVM addresses are accepted. Rate limit: 20 requests per minute.

## Endpoint

### `POST /v1/dapps/sign-personal-message`

Authentication: bearer token required.

EIP-191 personal_sign: returns a server signature for custodial wallets, or the digest to sign for wallets without a server-held key.

**Request body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `message` | string | yes | Message to sign: `0x`-prefixed hex bytes, or plain UTF-8 text. |
| `walletAddress` | string | yes | One of the caller's EVM wallet addresses (`0x` + 40 hex characters). |

**Responses**

`200` Custodial

```json
{
  "signature": "0x…65-byte signature, 130 hex characters…"
}
```

`200` Client Signing

```json
{
  "requiresClientSigning": true,
  "messageHash": "0x…32-byte EIP-191 digest…",
  "message": "Sign in to Example DEX"
}
```

`404` Not Found

```json
{
  "error": "WALLET_NOT_FOUND",
  "message": "No wallet found for address '0x71C7656EC7ab88b098defB751B7401B5f6d8976F'"
}
```

`500` Key Error

```json
{
  "error": "KEY_DECRYPTION_FAILED",
  "message": "Unable to access the signing key. Please try again later."
}
```

## Wallets Without a Server Key

`messageHash` is the full EIP-191 digest — `keccak256("\x19Ethereum Signed Message:\n" + length + messageBytes)`. The prefix is already applied, so sign it as a raw 32-byte digest; do **not** pass it to `signMessage`, which would prefix it a second time. Signing the digest directly gives the same signature as `signMessage(messageBytes)` on the original message.

For MPC wallets, the digest is what the threshold signing ceremony signs — see [Start Signing Ceremony](https://docs.aureahub.com/docs/mpc-sign-start.md). The API does not accept the resulting signature back: return it to the dApp.

## Implementation

```javascript
// Called by the dApp browser for personal_sign(message, address)
// localWallet: ethers Wallet for client_side wallets (unused for custodial ones)
async function handlePersonalSign(token, message, walletAddress, localWallet) {
  const res = await fetch('https://api.aureahub.com/v1/dapps/sign-personal-message', {
    method: 'POST',
    headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ message, walletAddress })
  });
  if (!res.ok) throw new Error((await res.json()).message);
  const result = await res.json();

  if (result.signature) return result.signature;   // signed by the server

  // requiresClientSigning: sign the digest as-is (it already carries the EIP-191 prefix)
  return localWallet.signingKey.sign(result.messageHash).serialized;
}
```

---

Web version: https://docs.aureahub.com/#dapps-sign
