# Формат взаимодействия внешнего кошелька и ESP32

Этот документ фиксирует первый этап формата взаимодействия между внешним браузерным wallet-расширением SHiNE и устройством `ESP32-S3-Touch-AMOLED-2.16`.

Документ описывает:

- как расширение получает текущий активный публичный ключ кошелька с ESP32;
- как расширение отправляет на ESP32 запрос подписи транзакции;
- что именно считается активным кошельком на ESP32;
- какие проверки и UI-реакции ожидаются в браузерном расширении и на устройстве;
- какие ограничения действуют в текущей версии протокола.

## 1. Общая идея

Устройство ESP32 хранит `master secret` пользователя и локально умеет выводить несколько кошельков из одного секрета.

На устройстве в UI пользователь выбирает текущий активный кошелёк:

- `client.key`
- `root.key`
- `custom`

Для `custom` используется derivation:

```text
sha256(base64(secret32) + "|wallet." + customName)
```

Браузерное расширение не указывает ESP32, какой кошелёк нужно вернуть в первом запросе. Оно просто спрашивает:

```text
какой кошелёк сейчас активен на устройстве
```

ESP32 возвращает:

- тип текущего активного кошелька;
- его публичный ключ `Base58`.

## 2. Транспорт и маршрут

Первая версия формата использует уже существующую `wallet-session` браузерного расширения.

Схема маршрута:

`browser extension -> SHiNE server -> homeserver session on ESP32 -> SHiNE server -> browser extension`

В первой версии:

- отдельная цифровая подпись payload не добавляется;
- отдельное E2E-шифрование для wallet RPC не добавляется;
- используется существующая авторизованная `wallet-session`, транспорт `WSS` и server-side маршрут через уже существующую операцию `CallSignalToSession`.

## 3. Запрос текущего публичного ключа кошелька

### 3.1. Назначение

Операция нужна, чтобы браузерное расширение могло узнать, какой кошелёк сейчас выбран на ESP32, и показать его пользователю перед дальнейшими действиями.

### 3.2. Формат запроса

```json
{
  "v": 1,
  "operation": "get_wallet_public_key",
  "requestId": "1718998123456-482193",
  "timeMs": 1718998123456
}
```

### 3.3. Поля запроса

- `v` — версия формата wallet RPC. Для текущего варианта: `1`.
- `operation` — строка операции. Для текущего запроса: `get_wallet_public_key`.
- `requestId` — идентификатор запроса, уникальный в пределах сеанса расширения. Рекомендуемый формат:
  `timeMs-random`.
- `timeMs` — локальное время отправителя в миллисекундах.

### 3.4. Поведение ESP32

При получении такого запроса ESP32:

1. смотрит, какой кошелёк сейчас выбран в локальном UI;
2. вычисляет или берёт уже подготовленный публичный ключ именно этого активного кошелька;
3. возвращает тип кошелька и его `publicKeyBase58`.

Запрос не содержит:

- `walletSelector`;
- `customName`;
- `targetSessionName`.

Они намеренно не входят в первую версию этого запроса.

## 4. Формат ответа

```json
{
  "v": 1,
  "op": "get_wallet_public_key_result",
  "requestId": "1718998123456-482193",
  "ok": true,
  "wallet": {
    "type": "custom",
    "publicKeyBase58": "...."
  },
  "timeMs": 1718998123999
}
```

## 5. Поля ответа

- `v` — версия формата ответа. Сейчас `1`.
- `op` — строка результата операции. Сейчас `get_wallet_public_key_result`.
- `requestId` — должен совпадать с `requestId` исходного запроса.
- `ok` — признак успешного результата.
- `wallet.type` — тип активного кошелька:
  - `client.key`
  - `root.key`
  - `custom`
- `wallet.publicKeyBase58` — публичный ключ активного кошелька в `Base58`.
- `timeMs` — время формирования ответа на стороне ESP32 в миллисекундах.

## 6. Ошибки первой версии

Минимальный формат ошибки в первой версии допускается таким:

```json
{
  "v": 1,
  "op": "get_wallet_public_key_result",
  "requestId": "1718998123456-482193",
  "ok": false,
  "error": "wallet_unavailable",
  "timeMs": 1718998123999
}
```

Рекомендуемые коды ошибок первой версии:

- `wallet_unavailable` — на устройстве нельзя получить текущий кошелёк;
- `secret_not_configured` — на устройстве ещё нет корректно сохранённого секрета;
- `wallet_type_unknown` — выбранный локальный тип кошелька не распознан;
- `internal_error` — прочая локальная ошибка устройства.

## 7. Правила для браузерного расширения

После ответа `ok=true` расширение должно:

1. показать пользователю тип кошелька;
2. показать полный `publicKeyBase58`;
3. дать кнопку копирования ключа в буфер;
4. сохранить этот ключ как текущий ключ устройства для следующей операции подписи.

### 7.1. Проверка через PDA Solana

Расширение уже знает публичные ключи пользователя из Solana PDA. Поэтому оно может дополнительно проверить ответ ESP32:

- если `wallet.type = client.key`, то `publicKeyBase58` должен совпасть с `clientKey`, прочитанным из PDA;
- если `wallet.type = root.key`, то `publicKeyBase58` должен совпасть с `rootKey`, прочитанным из PDA;
- если `wallet.type = custom`, такой проверки по PDA в первой версии нет.

При несовпадении для `client.key` или `root.key` расширение должно показать пользователю предупреждение, что возвращённый ключ не совпал с ожидаемым ключом из PDA.

## 8. Ожидаемое поведение UI расширения

### 8.1. Общий вид popup

Popup браузерного расширения должен быть узким и вытянутым по вертикали.

### 8.2. Состояние без подключения

Если `wallet-session` ещё не подключена:

- показывается кнопка `Подключить`;
- по нажатию открывается экран подключения, близкий по смыслу к сценарию `Войти через другое устройство`;
- пользователь вводит логин устройства и получает код подключения.

### 8.3. Состояние после подключения

Если `wallet-session` уже подключена:

- показывается статус `Подключено`;
- остаётся выбор homeserver;
- появляется кнопка запроса текущего кошелька;
- появляется кнопка `Отключить`.

### 8.4. Подключение кошелька с сайта

Когда сайт просит подключить кошелёк через расширение, расширение должно вести себя как обычный wallet extension:

1. показать пользователю подтверждение подключения;
2. показать, какой именно кошелёк будет подключён;
3. после подтверждения пользователя завершить подключение;
4. если пользователь отказался, не подключать кошелёк к сайту.

## 9. Запрос подписи транзакции

### 9.1. Назначение

Операция нужна, чтобы браузерное расширение могло запросить у ESP32 подпись Solana-транзакции текущим активным кошельком.

Расширение передаёт:

- публичный ключ, которым ожидается подпись;
- сериализованную транзакцию;
- комментарий, который должен быть показан на экране ESP32.

ESP32:

1. показывает пользователю запрос подтверждения;
2. показывает комментарий к подписи;
3. после нажатия `APPROVE` или `REJECT` возвращает ответ в расширение.

### 9.2. Формат запроса

```json
{
  "v": 1,
  "operation": "sign_transaction",
  "requestId": "1718998123456-482193",
  "timeMs": 1718998123456,
  "publicKeyBase58": "....",
  "transactionBase64": "....",
  "comment": "Site https://example.com requested transaction signature"
}
```

### 9.3. Поля запроса

- `v` — версия wallet RPC. Сейчас `1`.
- `operation` — строка операции: `sign_transaction`.
- `requestId` — идентификатор запроса.
- `timeMs` — время отправки на стороне расширения.
- `publicKeyBase58` — публичный ключ, от которого ожидается подпись.
- `transactionBase64` — сериализованная Solana transaction в `base64`.
- `comment` — короткое текстовое описание, которое ESP32 показывает пользователю при запросе подписи.

### 9.4. Поведение ESP32

При получении такого запроса ESP32:

1. сравнивает `publicKeyBase58` с публичным ключом текущего активного выбранного кошелька;
2. если ключ не совпадает, сразу возвращает ошибку `wallet_mismatch`;
3. если ключ совпадает, показывает отдельный экран подтверждения подписи;
4. на экране показывает:
   - каким кошельком будет выполнена подпись;
   - комментарий `comment`;
   - кнопки `APPROVE` и `REJECT`;
5. если пользователь подтверждает подпись, ESP32 подписывает транзакцию и возвращает результат;
6. если пользователь отклоняет, ESP32 возвращает `rejected_by_user`.

## 10. Формат ответа на подпись

```json
{
  "v": 1,
  "op": "sign_transaction_result",
  "requestId": "1718998123456-482193",
  "ok": true,
  "publicKeyBase58": "....",
  "signatureBase58": "....",
  "signedTransactionBase64": "....",
  "timeMs": 1718998123999
}
```

Если пользователь отклонил запрос:

```json
{
  "v": 1,
  "op": "sign_transaction_result",
  "requestId": "1718998123456-482193",
  "ok": false,
  "error": "rejected_by_user",
  "timeMs": 1718998123999
}
```

Рекомендуемые ошибки для `sign_transaction`:

- `rejected_by_user`
- `wallet_unavailable`
- `wallet_mismatch`
- `transaction_base64_invalid`
- `transaction_sign_failed`
- `bad_request`

## 11. Подключение кошелька с сайта

При вызове сайта `connect wallet` расширение должно вести себя как обычный wallet extension:

1. запросить подтверждение у пользователя в браузере;
2. получить текущий публичный ключ с ESP32;
3. вернуть сайту `publicKey` текущего активного кошелька.

Для `signTransaction` расширение:

1. получает транзакцию от сайта;
2. пересылает её на ESP32 через `sign_transaction`;
3. ждёт решение пользователя на устройстве;
4. возвращает браузеру уже подписанную транзакцию.

## 12. Ограничения текущей версии

- запрос возвращает только текущий активный кошелёк, а не список всех кошельков;
- выбор типа кошелька делается только на самом ESP32;
- отдельная цифровая подпись ответа пока не используется;
- отдельное E2E-шифрование wallet RPC пока не используется;
- `custom`-кошельки в первой версии не сверяются с PDA.
