# Формат взаимодействия внешнего кошелька и 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.