Skip to main content
В Tonhub можно использовать аппаратный кошелек Ledger для хранения ключей и подписи транзакций в сети TON. Ниже описано, как работает эта интеграция: подключение, выбор аккаунта, подписание операций, ограничения и типичные ошибки.

Назначение и возможности

Ledger — это аппаратное устройство, холодный кошелек, такой как Nano S, Nano X, Nano S Plus и другие, на котором установлено приложение TON через Ledger Live. Ключи никогда не покидают устройство, а подтверждение переводов и подписей происходит непосредственно на Ledger.
В Tonhub Ledger используется как отдельный тип кошелька: пользователь подключает устройство по USB, то есть HID, на Android или по Bluetooth, то есть BLE, на iOS и Android, выбирает один из аккаунтов с индексами 0-9 и после этого может просматривать баланс, отправлять TON и токены, стейкать, пользоваться Holders и подключать dApp через TON Connect. Подписание транзакций и данных всегда выполняется на Ledger. Подключать можно неограниченное количество холодных кошельков.
Минимальная версия приложения TON на Ledger — 2.4.1. Если установлена более старая версия, приложение покажет сообщение с просьбой обновить TON-приложение через Ledger Live.

Подключение Ledger

Добавление устройства

  1. Пользователь на приветственном экране выбирает “Import Wallet” или аналогичный пункт и на экране выбора метода импорта нажимает “Connect Ledger”.
  2. Открывается Ledger Onboarding, то есть LedgerOnboardingFragment: краткое описание и кнопка “Continue”. По ссылке можно перейти к инструкции по установке TON-приложения: https://tonwhales.com/ledger
  3. Пользователь принимает условия и политику, то есть LegalCreate с параметром ledger: true, после чего попадает на экран создания кошелька с настройкой PIN-кода, то есть WalletSecurePasscodeComponent с флагом ledger. Этот PIN нужен для доступа к приложению, а не к ключам на Ledger.
  4. После настройки PIN пользователь переходит на экран подключения устройства, то есть HardwareWalletFragment, который в навигации отображается как “Ledger”.

Экран подключения (HardwareWalletFragment)

Android: доступны два способа:
  • USB (HID): кнопка “Connect via USB”. Устройство подключается кабелем, приложение создает HID transport и переходит к выбору аккаунта, то есть LedgerSelectAccount, либо сразу возвращает пользователя в приложение, если аккаунт уже был выбран.
  • Bluetooth: кнопка “Connect via Bluetooth”. Запускается поиск BLE-устройств; при необходимости приложение запрашивает разрешения, например доступ к геолокации на Android для BLE.
iOS: доступен только Bluetooth. Кнопка “Connect” запускает поиск Ledger через BLE; требуются Bluetooth-разрешения.
  • На экране также показываются краткие инструкции и ссылка на руководство по установке TON-приложения на Ledger по адресу tonwhales.com/ledger.

Выбор BLE-устройства (LedgerDeviceSelectionFragment)

После запуска поиска BLE отображается список найденных устройств. Пользователь выбирает свой Ledger.
Приложение подключается к выбранному устройству через TransportBLE.open(device.id) и сохраняет соединение в контексте TransportContext. Если выбранный аккаунт еще не задан, пользователь переходит на экран выбора аккаунта, то есть LedgerSelectAccount.
При ошибке доступа, например если на Android не выданы разрешения на геолокацию, показывается сообщение с кнопкой “Grant Permissions”; при необходимости открываются настройки приложения.

Выбор аккаунта (LedgerSelectAccountFragment)

  • Ledger поддерживает несколько TON-аккаунтов, индексы 0-9; в коде используется pathFromAccountNumber(i, isTestnet). Приложение запрашивает адреса для каждого индекса, максимум до 10, и показывает список с балансами.
  • Пользователь должен разблокировать Ledger и открыть на устройстве приложение TON. Пока TON-приложение закрыто или устройство заблокировано, экран показывает подсказки вроде “Unlock Ledger” или “Open TON application”.
  • Проверка готовности выполняется через isLedgerTonAppReady(tonTransport): приложение опрашивает состояние “app open” и при необходимости запрашивает адрес для индекса 0, включая обход особенностей Ledger S.
  • Пользователь выбирает аккаунт из списка. Выбранный аккаунт сохраняется в контексте и хранилище через setLedgerSelected(addr.address); список Ledger-кошельков ledgerWallets обновляется, после чего происходит переход в основное Ledger-приложение, то есть LedgerApp.

Повторное подключение и переключение кошельков

На главном экране, то есть HomeFragment, по нажатию на карточку кошелька или через меню открывается выбор аккаунта, то есть AccountSelectorFragment. В списке отображаются и обычные кошельки, и ранее добавленные аккаунты Ledger из ledgerContext.wallets.
Если Ledger уже подключен, то есть есть tonTransport, при выборе “Ledger” открывается экран выбора аккаунта LedgerSelectAccount с текущим выбранным адресом.
Если Ledger не подключен, выбор “Ledger” вызывает ledgerContext.reset() и открывает экран подключения HardwareWalletFragment. После BLE-подключения пользователь снова переходит к выбору аккаунта; после HID-подключения — к выбору аккаунта или обратно, если аккаунт уже был выбран.

Основной интерфейс Ledger

После выбора аккаунта пользователь попадает в LedgerApp — полноэкранный режим с нижними вкладками:
  • Home (LedgerHome) — карточка кошелька, баланс, продукты, такие как переводы, получение, стейкинг, обмены, Holders и другие, а также переключатель режимов Holders и Wallet, то есть AppModeToggle.
  • History (LedgerTransactions) — список транзакций. Режим зависит от типа аккаунта: для обычного кошелька это TransactionsFragment, а для Holders — HoldersTransactionsFragment.
  • Settings (LedgerSettings) — для обычного кошелька открывается SettingsFragment, а в режиме Holders — HoldersSettings.
Состояние выбранного Ledger-аккаунта хранится в приложении через ledgerSelected в appState. При следующем запуске, если выбран Ledger, onboarding сразу переводит пользователя в состояние LedgerApp, и он попадает туда без повторного выбора, если в ledgerContext.wallets уже есть сохраненный аккаунт и восстановлен ledgerContext.addr.

Подписание транзакций

Когда пользователь отправляет TON или токены с Ledger-кошелька, приложение формирует order операции, то есть LedgerOrder, и открывает экран подписи LedgerSignTransfer. Пользователь видит сводку: получатель, сумма, комиссия и при необходимости данные токена.
Подписание выполняется через tonTransport, то есть TonTransport из @ton-community/ton-ledger: строится сообщение и вызывается метод подписи на устройстве через путь аккаунта pathFromAccountNumber(addr.acc, isTestnet).
Важно: на Ledger должно быть открыто TON-приложение, а само устройство должно быть разблокировано. Иначе подпись завершится ошибкой.

Обработка ошибок подписи

  • LockedDeviceError: показывается сообщение “Unlock Ledger”.
  • Нет соединения, то есть !tonTransport: приложение возвращает пользователя назад и показывает диалог “Ledger connection error” с кнопками “Connect” и “Cancel”.
  • TON-приложение не открыто: после проверки isLedgerTonAppReady показывается сообщение с просьбой открыть TON-приложение на Ledger.
  • Версия TON-приложения ниже 2.4.1: проверяется через checkLedgerTonAppVersion; пользователь получает сообщение о необходимости обновить TON-приложение в Ledger Live.
  • “Небезопасная” транзакция при отключенном Blind Signing: если операция помечена как unsafe и в настройках Ledger отключена подпись неподтвержденных данных, показывается сообщение наподобие “Enable unconfirmed data signing in TON app settings on Ledger”.
  • Пользователь отменил операцию на Ledger, код 0x6985: показывается сообщение о том, что подпись была отменена.
Во всех остальных случаях отображается общее сообщение “Transaction rejected” и кнопка “Back”.

Подписание данных

Для dApp и TON Connect-сценариев может потребоваться подпись произвольных данных, например для аутентификации. Экран LedgerSignData позволяет ввести данные, в том числе в Base64, домен и extension, если это нужно, а затем подписать их на Ledger через tonTransport.signData с типом app-data.
Используется текущий путь аккаунта; результат, то есть подпись, cell и timestamp, показывается на экране. Требования те же: устройство должно быть разблокировано, а TON-приложение открыто.

Потеря соединения и повторное подключение

При потере соединения, например при отключении USB, выключении Bluetooth или самого Ledger, вызывается обработчик onDisconnect. Приложение пытается переподключиться ограниченное число раз: для HID — один раз, для BLE — два раза. Во время попыток может показываться состояние переподключения isReconnectLedger.
Если переподключение не удалось, BLE-состояние сбрасывается в ошибку и вызывается reset(): соединение и выбранный адрес очищаются из контекста, а пользователю нужно снова подключить Ledger и, при необходимости, заново выбрать аккаунт.
При явном выходе из Ledger-кошелька показывается диалог подтверждения; после согласия выбранный Ledger-аккаунт удаляется из списка кошельков, выбор очищается через clearLedgerSelected. Если это был последний Ledger-кошелек, контекст полностью сбрасывается.

Ограничения и отличия от обычного кошелька

DeDust: если выбран Ledger-кошелек, в разделе “Exchanges”, то есть SelectExchangeFragment, элемент DeDust.io не показывается, то есть !isLedger. Доступен только Changelly. Причина в том, что интеграция DeDust через WebView и TON Connect в текущем виде не поддерживает Ledger.

Хранение данных

  • Список Ledger-кошельков ledgerWallets: массив объектов вида { acc, address, deviceId?, publicKey }, который хранится в storage по ключу ledgerWalletsKey. При выборе аккаунта он добавляется в список, если еще не был добавлен.
  • Выбранный Ledger-аккаунт: адрес сохраняется через setLedgerSelected(addr.address) в appState. При следующем входе в LedgerApp этот адрес восстанавливается.
  • Включенная поддержка Ledger в приложении: флаг app_ledger_enabled в storage; он используется для отображения или скрытия опции Ledger в списке кошельков, например в настройках или при первом выборе способа входа.
  • Ключи и seed-фраза не хранятся в приложении; они остаются только на Ledger.