# Интеграция провайдера

Запускайте провайдер URnetwork в приложении. Каждая установка раздаёт соединение как клиент вашей сети и зарабатывает на один постоянный кошелёк Bittensor.

![Интеграция провайдера — ваш бэкенд оформляет каждую провайдерскую установку для вашего приложения на устройстве пользователя, приложение раздаёт соединение через URnetwork клиентам, а согласие сети от владельца coldkey, один раз подписанное офлайн и отправленное вашим бэкендом, направляет выплаты подсети на постоянный coldkey для выплат; ни один ключ не поставляется внутри приложения](/docs-assets/diagram-dev-integrate-provider.svg)

Провайдер раздаёт интернет-соединение устройства: трафик других пользователей URnetwork выходит через него. Пока он работает, приложение показывает четыре вещи: идёт ли раздача, скольких клиентов он обслужил, сколько данных передал и кошелёк для выплат, только для чтения. Итоги заработка берутся из вашего бэкенда. Готовые примеры есть для двенадцати языков и трёх собственных платформ: Electron, Tauri и Android. Все они реализуют [контракт провайдера](https://github.com/urnetwork/examples/blob/main/PROVIDER_CONTRACT.md), где закреплены точные правила; эта страница объясняет, как части сочетаются друг с другом.

## Кто что хранит

Интеграция провайдера делит работу между тремя сторонами, и ни один ключ не поставляется внутри приложения.

- **Ваш бэкенд** хранит корневой JWT вашей сети. Он оформляет каждую установку как провайдерскую установку (provider install), передаёт каждой установке её ограниченный клиентский JWT и запрашивает сообщения согласия для кошелька.
- **Владелец coldkey** подписывает каждое согласие для кошелька офлайн, своим собственным кошельковым инструментом, например btcli, расширением polkadot.js или subkey. Секрет coldkey никогда не попадает ни на ваш бэкенд, ни в приложение.
- **Приложение** запускает локальный Device SDK со своим клиентским JWT, публично раздаёт соединение и показывает статус. Оно никогда не хранит корневой JWT и никогда не подписывает и не отправляет согласие.

| Значение | Где хранится | Назначение |
| --- | --- | --- |
| `URNETWORK_ROOT_JWT` | Хранилище секретов вашего бэкенда | Создаёт провайдерские установки и привязывает их к кошельку для выплат |
| Адрес coldkey для выплат (ss58) | Конфигурация вашего бэкенда | Постоянный кошелёк для выплат: публичный, но задаёт его только бэкенд |
| Секрет coldkey | У владельца, офлайн | Подписывает согласия для кошелька |
| `client.jwt` | Приватное состояние установки | Ограниченные учётные данные установки, которые обновляет SDK |

Выплаты приходят на coldkey как выплаты подсети Bittensor. Приложение читает кошелёк для выплат только для показа, и собственных настроек выплат у него нет.

## Сначала согласие

При раздаче трафик других пользователей выходит через соединение пользователя, поэтому приложение должно спросить согласия до начала раздачи и позволять остановить её в любой момент. Каждый пример показывает это предупреждение дословно, а контракт закрепляет его SHA-256:

```text
Consent disclaimer: an app that integrates a URnetwork provider must collect the user's consent before it provides.
Providing shares the user's internet connection: other URnetwork users' traffic exits through the user's device and IP address.
This example starts providing without asking, because the consent screen belongs to your app.
```

Консольные примеры выводят его при запуске. Приложения с графическим интерфейсом и приложение для Android показывают его рядом с элементом управления, который запускает раздачу. Своего экрана согласия у примеров нет, потому что этот экран принадлежит вашему приложению, и пропускную способность они не ограничивают.

## Создание провайдерских установок

Каждая установка — это **провайдерская установка** (provider install): клиент верхнего уровня вашей сети, который только раздаёт соединение. Ваш бэкенд создаёт её запросом `POST /network/auth-client` с корневым JWT, передавая описание, спецификацию устройства и флаг `provide_intent`, но без `client_id` и `source_client_id`:

```json
{
  "description": "provider user:alice:laptop-1",
  "device_spec": "urnetwork-examples/go-provider",
  "provide_intent": true
}
```

Ответ содержит новый `client_id` и его ограниченный `by_client_jwt`, который бэкенд передаёт установке как её `client.jwt`. Сервер хранит флаг вместе с клиентом всё время его жизни:

- Провайдерская установка никогда не появляется в списке пиров вашей сети (`GET /network/peers` и поток изменений пиров) и никогда не получает этот список.
- Она не учитывается ни в лимите из 100 клиентов верхнего уровня, ни среди 100 недавно активных клиентов верхнего уровня, которые держат список пиров включённым.
- Её создание не подпадает ни под этот лимит, ни под проверку одновременных клиентов вашего тарифа.

Флаг не заставляет устройство раздавать соединение. SDK сам объявляет намерение раздачи (provide intent) на своих соединениях, когда устройство раздаёт публично, и правила лимита клиентов ниже действуют для соединений, которые его объявляют. Приложениям ничего настраивать не нужно. Провайдерская установка, которая подключается без публичной раздачи, считается обычным клиентом.

Чтобы перевыпустить токен, отправьте сохранённый `client_id` с описанием и спецификацией устройства. При перевыпуске сервер игнорирует `provide_intent`, поэтому отправляйте флаг только при создании. Клиент, который не подключался 30 дней, деактивируется: на его перевыпуск сервер отвечает `Client does not exist.`, и тогда бэкенд создаёт новую провайдерскую установку.

Два вида клиентов не являются провайдерскими установками. Собственное устройство человека, которое тоже раздаёт соединение, само объявляет намерение и остаётся пиром сети, поэтому флаг для него не ставьте. Дочерний клиент, созданный с `source_client_id`, никогда не предлагается другим пользователям как провайдер и ничего не зарабатывает.

[Инструмент бэкенда на Go](https://github.com/urnetwork/examples/blob/main/go/provider/README.md#backend-provision-and-map-the-payout-wallet) делает всё это командой `wallet provision <installation-key> <client-jwt-file>`, а [контракт](https://github.com/urnetwork/examples/blob/main/PROVIDER_CONTRACT.md#backend-provision-provider-clients) показывает те же запросы через curl.

## Провайдеры и лимит клиентов

Провайдеры не подпадают под лимит одновременных клиентов вашего тарифа, пока проходят квалификацию и после того, как её прошли. Провайдер проходит квалификацию при трёх условиях:

- публичный режим раздачи с зарегистрированными ключами раздачи (provide keys);
- пройденная проверка состояния выхода (egress-health);
- после 12 часов истории — надёжность за 12 часов не ниже 0.7.

Новому провайдеру даётся 1 час льготного периода на квалификацию, и этот период никогда не заканчивается раньше, чем будет предпринята хотя бы одна проверка выхода. Квалифицированный провайдер перепроверяется каждые 4 часа. У каждого клиента одна попытка квалификации раз в 8 часов.

Провайдер, не прошедший квалификацию, занимает обычное место клиента, если оно свободно. Если свободных мест нет, платформа может его отключить. Тогда SDK удерживает все соединения этого клиента с платформой на 15 минут плюс до 5 минут случайного разброса (jitter) и затем сам подключается снова. В это время приложение показывает `client limit, retry at HH:MM UTC`: время повторной попытки в UTC, округлённое вверх до целой минуты. Больше ничего о квалификации платформа клиенту не сообщает.

## Привязка кошелька для выплат

Кошелёк для выплат — это ваш постоянный coldkey Bittensor в виде адреса ss58. Сервер платит клиенту-провайдеру только по согласию на привязку кошелька, которое подписал владелец coldkey. Согласия бывают двух видов:

| Согласие | Одна подпись покрывает | Для чего |
| --- | --- | --- |
| Согласие сети | Всех клиентов-провайдеров вашей сети, нынешних и будущих | Приложения со встроенным провайдером: владелец подписывает один раз на сеть |
| Согласие провайдера | Одного клиента-провайдера | Клиент, который зарабатывает на другой coldkey, например под управлением независимого подписанта |

Для каждого клиента-провайдера и каждой эпохи побеждает действующее согласие провайдера; иначе платит согласие сети. Согласие покрывает эпохи от своего `from_epoch` до своего `through_epoch`, не более 65 536, и действует только вперёд. Чтобы сменить coldkey, подпишите новое согласие того же вида; прошлые эпохи сохраняют прежний coldkey. Отзыва без подписи не существует.

Каждое согласие проходит четыре шага, и только подпись приходит извне вашего бэкенда:

1. Прочитайте текущую эпоху `E` из `GET /sn/epoch`.
2. Запросите сообщение с корневым JWT: `POST /sn/wallet/network-consent` с coldkey и эпохами от `E+1` до `E+65536` или `POST /sn/wallet/consent`, дополнительно с `client_id`.
3. Владелец coldkey подписывает точные байты сообщения в течение пяти минут.
4. Отправьте coldkey, сообщение и подпись в `POST /sn/wallet`, а для согласия провайдера — ещё и тот же `client_id`. Проверьте привязку через `GET /sn/wallet`.

Подпись — это sr25519 в контексте подписи `substrate` над байтами сообщения в UTF-8, в исходном виде или в обёртке `<Bytes>…</Bytes>`. Обёрнутую форму подписывает `signRaw` типа `bytes` в расширении Polkadot. Подпись занимает 64 байта и передаётся в шестнадцатеричном виде. Сообщение согласия сети начинается с `Approve URnetwork network wallet mapping`, а согласия провайдера — с `Approve URnetwork provider wallet mapping`.

[Кошельковый инструмент на Go](https://github.com/urnetwork/examples/blob/main/go/provider/README.md#backend-provision-and-map-the-payout-wallet) работает с обоими видами: `network-challenge` и `network-accept` для согласия сети, `challenge` и `accept` для одного клиента и `show` для просмотра. Сервер без согласий сети отвечает на `POST /sn/wallet/network-consent` кодом 404; согласия провайдеров там по-прежнему работают. Третий, младший режим, делегирование hotkey, появится с более поздним изменением сервера и SDK. Приложения лишь помечают его как `(hotkey)`.

## Внутри приложения

Каждая установка хранит своё состояние в одном приватном каталоге: `client.jwt`, `instance-id`, `identity.json` и ограниченные по размеру `logs/` SDK. Консольные примеры берут абсолютный путь каталога из `URNETWORK_PROVIDER_STATE_DIR`; приложения с графическим интерфейсом и для Android используют свои приватные данные приложения. В POSIX у каталога права 0700, у его файлов — 0600, и каждый файл заменяется атомарно. `identity.json` сохраняет ключи провайдера между перезапусками.

Жизненный цикл приложения короткий:

1. Покажите предупреждение о согласии и загрузите состояние установки.
2. Создайте Device конструктором SDK с поддержкой экстендера и ключевым материалом установки. При первом запуске его ещё нет: Device создаёт новую идентичность, которую приложение сохраняет как `identity.json`.
3. Сохраняйте каждый обновлённый токен как `client.jwt` и останавливайтесь при выходе из аккаунта.
4. Включите публичный режим раздачи. После этого SDK сам объявляет намерение раздачи.
5. Читайте статус каждую секунду, а кошелёк для выплат — при запуске и каждые 10 минут.

Каждый пример показывает одни и те же четыре поля по одним и тем же правилам:

| Поле | Показывает |
| --- | --- |
| Статус | `stopped`, `client limit`, `paused`, `providing` или `starting`, проверяются в этом порядке |
| Обслужено клиентов | Разные клиенты, открывшие контракт с этим провайдером с момента запуска приложения |
| Передано данных | Байты, переданные для клиентов в обе стороны, в двоичных единицах |
| Кошелёк для выплат | Coldkey с его областью: `(this provider)`, `(network)`, `(hotkey)` или `(another provider)` |

Консольные примеры выводят строку статуса, когда меняется статус, число обслуженных клиентов или кошелёк для выплат, а в остальное время раз в минуту:

```text
status: providing | clients served: 3 | data provided: 12.4 MiB | payout wallet: 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY (network)
```

Для `providing` раздача должна быть включена, а соединение с платформой установлено. Кошелёк показывает `checking` до первого чтения, `unavailable`, если это чтение не удалось, и `not set`, если кошелёк не привязан. Адрес выше — публичный аккаунт разработки Substrate, используемый только как пример данных. [Эталонные векторы контракта](https://github.com/urnetwork/examples/blob/main/PROVIDER_CONTRACT.md#status) точно закрепляют каждый текст.

## Роль экстендера

Пока устройство раздаёт соединение, Device также выполняет роль экстендера провайдера, которая включена по умолчанию. Она слушает TCP 443, а также UDP 443 и 4053, чтобы клиенты, которые не могут напрямую достучаться до платформы, подключались через этого провайдера. UDP 53 дополнительно слушает только консольный провайдер подсети (`provider provide`), а клиенты пробуют и UDP 53, и 4053. Windows и macOS при первом запуске могут спросить пользователя, разрешить ли входящие соединения. В Linux процесс без права занимать эти порты продолжает раздачу без этой роли. Сборки SDK для Android и iOS её не включают.

Значение по умолчанию выключает одна настройка: `DefaultProvideExtender` со значением false.

| Биндинг | По умолчанию выключено |
| --- | --- |
| Go | `DeviceLocalSettings.DefaultProvideExtender = false` или `false` последним аргументом конструктора |
| gomobile | `Sdk.newDeviceLocalWithProvideExtender` (Java, Kotlin) или `SdkNewDeviceLocalWithProvideExtender` (Swift) со значением по умолчанию false |
| C ABI | `urnet_new_device_local_with_provide_extender` с `default_provide_extender`, равным false |
| C++ | `urnet::newDeviceLocalWithProvideExtender` со значением по умолчанию false |

Собственный выбор пользователя, заданный через `SetProvideExtender`, по-прежнему важнее значения по умолчанию. `ProvideExtenderEnabled` со значением false — главный выключатель: роль не запускается никогда. Обе настройки и принимающий их конструктор есть в SDK начиная с первого релиза после sdk `c638dfa8`.

## Работа в фоне

Консольные примеры используют три общих [шаблона для работы в фоне](https://github.com/urnetwork/examples/blob/main/background/README.md): пользовательский сервис systemd в Linux, агент launchd в macOS и запланированную задачу в Windows. Каждый шаблон указывает каталог состояния установки, поэтому определение сервиса содержит путь, а не учётные данные. Все примеры используют одни и те же коды выхода:

| Код выхода | Значение |
| --- | --- |
| 0 | Запрошенная остановка, например Ctrl-C или SIGTERM |
| 78 | Проблема конфигурации или учётных данных, которую перезапуск не исправит |
| 1 | Любой другой сбой |

Супервизоры перезапускают провайдера после сбоя, но не после 0 или 78. У launchd нет правил для отдельных кодов выхода: после 78 он повторяет попытку раз в минуту, пока состояние не исправят.

Приложения с графическим интерфейсом и для Android продолжают раздачу сами. Electron и Tauri остаются в трее, когда окно закрыто, и предлагают запуск при входе в систему. Android раздаёт соединение из сервиса переднего плана (foreground service), который запускает пользователь, и приостанавливает раздачу в сетях с тарификацией трафика.

## Примеры

README каждого примера содержит шаги сборки и запуска для каждой поддерживаемой ОС и самопроверку без учётных данных. Консольные примеры работают в Windows, macOS и Linux. [Провайдер на Go](https://github.com/urnetwork/examples/blob/main/go/provider/README.md) — эталонная реализация.

| Платформа | Пример | Основа |
| --- | --- | --- |
| C | [Провайдер на C](https://github.com/urnetwork/examples/blob/main/c/provider/README.md) | C ABI |
| C++ | [Провайдер на C++](https://github.com/urnetwork/examples/blob/main/cpp/provider/README.md) | C ABI |
| C# | [Провайдер на C#](https://github.com/urnetwork/examples/blob/main/csharp/provider/README.md) | C ABI через .NET |
| Go | [Провайдер на Go](https://github.com/urnetwork/examples/blob/main/go/provider/README.md) | Go SDK |
| Java | [Провайдер на Java](https://github.com/urnetwork/examples/blob/main/java/provider/README.md) | C ABI через JNA |
| JavaScript | [Провайдер на JavaScript](https://github.com/urnetwork/examples/blob/main/javascript/provider/README.md) | Node и нативный компаньон |
| Kotlin | [Провайдер на Kotlin](https://github.com/urnetwork/examples/blob/main/kotlin/provider/README.md) | C ABI через JNA |
| Python | [Провайдер на Python](https://github.com/urnetwork/examples/blob/main/python/provider/README.md) | C ABI через ctypes |
| Ruby | [Провайдер на Ruby](https://github.com/urnetwork/examples/blob/main/ruby/provider/README.md) | C ABI через FFI |
| Rust | [Провайдер на Rust](https://github.com/urnetwork/examples/blob/main/rust/provider/README.md) | C ABI через крейт Rust |
| Swift | [Провайдер на Swift](https://github.com/urnetwork/examples/blob/main/swift/provider/README.md) | C ABI через C-модуль SwiftPM |
| TypeScript | [Провайдер на TypeScript](https://github.com/urnetwork/examples/blob/main/typescript/provider/README.md) | Node и нативный компаньон |
| Electron | [Провайдер на Electron](https://github.com/urnetwork/examples/blob/main/electron/provider/README.md) | Десктопное приложение на нативном компаньоне |
| Tauri | [Провайдер на Tauri](https://github.com/urnetwork/examples/blob/main/tauri/provider/README.md) | Десктопное приложение с крейтом Rust в том же процессе |
| Android | [Провайдер для Android](https://github.com/urnetwork/examples/blob/main/android/provider/README.md) | AAR из gomobile в сервисе переднего плана |

**Доступность релиза:** примерам провайдера нужен релиз SDK с API провайдера: конструктором с поддержкой экстендера, статусом лимита клиентов и областью согласия для кошелька. Они есть в SDK начиная с первого релиза после sdk `c638dfa8`. Пакеты описаны в [руководстве по установке SDK](/docs/install-sdk), а остальные примеры каждого языка — в [обозревателе примеров](/docs/examples).
