> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbitflare.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Клиент Apex на Rust

> apex-sender-client является крейтом Rust для OrbitFlare Apex: постоянное QUIC-соединение с переподключениями 0-RTT, а также HTTP-хелперы для JSON-RPC и пакетов.

`apex-sender-client` является эталонным клиентом для Apex. Он держит одно постоянное QUIC-соединение на каждый эндпоинт Apex, аутентифицируется клиентским сертификатом, производным от вашего API-ключа, отправляет одну сериализованную транзакцию на поток и переподключается с 0-RTT при обрыве соединения.

## Установка

Крейт **скоро появится на crates.io** под именем `apex-sender-client`. Пока он не опубликован, устанавливайте его из GitHub-репозитория OrbitFlare:

```toml theme={null}
[dependencies]
apex-sender-client = { git = "https://github.com/orbitflare/apex-sender-client", features = ["rpc"] }
```

Когда он появится на crates.io:

```toml theme={null}
[dependencies]
apex-sender-client = { version = "0.1", features = ["rpc"] }
```

Фича `rpc` добавляет HTTP-хелперы: `getTipAccounts`, JSON-RPC `sendTransaction`, бинарные маршруты, а также хелпер для blockhash и подтверждения на любом Solana RPC. Сам путь QUIC не имеет HTTP-зависимостей, поэтому не включайте эту фичу, если вам нужен только QUIC.

Крейт требует Rust 1.92 или новее.

## Быстрый старт

```rust theme={null}
use std::time::Duration;

use apex_sender_client::rpc::{RpcClient, SolanaRpc};
use apex_sender_client::{tip, tip_instruction, ApexSenderClient, Region, MIN_TIP_LAMPORTS};
use solana_signer::Signer;
use solana_transaction::versioned::VersionedTransaction;
use solana_transaction::Transaction;

let client = ApexSenderClient::connect(Region::Frankfurt, &api_key).await?;
let tip_accounts = RpcClient::new(Region::Frankfurt, &api_key).get_tip_accounts().await?;
let tip_account = tip::pick_tip_account(&tip_accounts).ok_or("no tip accounts")?;

// Your instructions, plus the tip as a top-level instruction.
let instructions = [
    your_instruction,
    tip_instruction(&payer.pubkey(), &tip_account, MIN_TIP_LAMPORTS),
];
let solana = SolanaRpc::new(solana_rpc_url);
let blockhash = solana.latest_blockhash().await?;
let tx = VersionedTransaction::from(Transaction::new_signed_with_payer(
    &instructions,
    Some(&payer.pubkey()),
    &[&payer],
    blockhash,
));

let signature = client.send_transaction(&tx).await?; // microseconds, no acknowledgement
let slot = solana.confirm(&signature.to_string(), Duration::from_secs(30)).await?;
```

Полную программу с `Cargo.toml` см. на вкладке Rust в разделе [Быстрый старт](/ru/apex/quickstart).

## Регионы

`Region` перечисляет все эндпоинты Apex. `Region::parse("fra")` и `Region::code()` преобразуют короткие коды в обе стороны, а `Region::ALL` содержит их список.

| Вариант                               | Код      |
| ------------------------------------- | -------- |
| `Region::Frankfurt`                   | `fra`    |
| `Region::Amsterdam`                   | `ams`    |
| `Region::London`                      | `lon`    |
| `Region::NewYork`                     | `nyc`    |
| `Region::SaltLakeCity`                | `slc`    |
| `Region::Singapore`                   | `sgp`    |
| `Region::Tokyo`                       | `tyo`    |
| `Region::Siauliai`                    | `sqq`    |
| `Region::Global` (в процессе запуска) | `global` |

`region.quic_endpoint()` возвращает `<code>.apex.orbitflare.com:7001`, а `region.rpc_url()` возвращает `http://<code>.apex.orbitflare.com`. См. [Эндпоинты и регионы](/ru/apex/endpoints).

## Транспорты

|                        | QUIC однонаправленный                        | QUIC двунаправленный                                                    | Бинарный HTTP                                           | JSON-RPC                           |
| ---------------------- | -------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------- | ---------------------------------- |
| **Вызов**              | `send_transaction`, `send_transaction_bytes` | `send_transaction_with_response`, `send_with_response`                  | `rpc::RpcClient::send_transaction_binary`, `send_batch` | `rpc::RpcClient::send_transaction` |
| **Возвращает**         | Подпись. В ответ ничего не читается          | Принято либо код и сообщение отклонения                                 | Подпись или ошибку с меткой                             | Подпись или ошибку JSON-RPC        |
| **Стоимость отправки** | Один поток в прогретом соединении            | Один поток плюс один обмен с эндпоинтом                                 | HTTP-запрос с сырыми байтами                            | HTTP-запрос с base64 в JSON        |
| **Лучше всего для**    | Ботов с постоянным соединением               | Интеграции, отладки, инструментов, которым нужна причина сразу в ответе | Пакетов                                                 | Замены в коде с `sendTransaction`  |

Транспорт не влияет на приоритет и маршрутизацию. На них влияют чаевые.

## API

| Элемент                                                                           | Описание                                                                                                                  |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `ApexSenderClient::connect(region, api_key)`                                      | Подключение с эфемерным локальным портом и параметрами по умолчанию                                                       |
| `ApexSenderClient::connect_with_options(opts, api_key)`                           | Подключение с [`ClientOptions`](#параметры)                                                                               |
| `send_transaction(&tx)`                                                           | Сериализует (legacy, v0 или v1) и отправляет в однонаправленном потоке. Возвращает первую подпись                         |
| `send_transaction_bytes(bytes)`                                                   | То же самое с байтами, которые у вас уже есть. Ничто не кодирует их заново                                                |
| `send_transaction_with_response(&tx)`                                             | Двунаправленный поток. `Err(Error::Rejected { code, message })` при отклонении                                            |
| `send_with_response(bytes)`                                                       | То же самое с байтами. Возвращает сырой `Admission`                                                                       |
| `health()`, `reconnects_total()`, `zero_rtt_resumptions_total()`, `remote_addr()` | Состояние соединения для ваших метрик                                                                                     |
| `reconnect()`, `close()`                                                          | Жизненный цикл                                                                                                            |
| `tip_instruction(payer, tip_account, lamports)`                                   | Собирает перевод чаевых                                                                                                   |
| `tip::pick_tip_account(&accounts)`                                                | Выбирает один из аккаунтов для чаевых случайным образом                                                                   |
| `MIN_TIP_LAMPORTS`                                                                | Минимум стандартного тарифа: 1,000,000 lamports                                                                           |
| `rpc::RpcClient`                                                                  | Эндпоинт Apex по HTTP: `get_tip_accounts`, `send_transaction` (JSON-RPC), `send_transaction_binary`, `send_batch`, `ping` |
| `rpc::SolanaRpc`                                                                  | Любой Solana RPC: `latest_blockhash`, `confirm(signature, timeout)`                                                       |
| `rpc::fetch_vaults(solana_rpc_url)`                                               | Читает аккаунты для чаевых напрямую из ончейн-программы чаевых (`rpc::TIP_PROGRAM_ID`), не обращаясь к эндпоинту Apex     |
| `client_pubkey(api_key)`                                                          | Ключ сертификата, который выводится из вашего API-ключа, как он показан в вашей панели управления                         |
| `serialize_transaction(&tx)`                                                      | Канонические байты для передачи. Совпадают с bincode для legacy и v0 и корректны для [v1](/ru/apex/transaction-v1)        |
| `wire::encode_packet`, `wire::decode_admission`                                   | Формат передачи QUIC как эталон для других языков                                                                         |

`Clone` клиента обходится дёшево, и клоны разделяют одно соединение. Создайте его один раз и раздайте клоны своим задачам.

<Note>
  В крейте пока нет хелпера для бандлов. Отправляйте [бандлы](/ru/apex/bundles) через `sendBundle` или `POST /send-bundle` по HTTP.
</Note>

## Параметры

`ClientOptions` реализует `Default`, поэтому задавайте только то, что вам нужно:

| Поле                  | По умолчанию       | Описание                                                                                                          |
| --------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------- |
| `endpoint`            | QUIC-адрес региона | `host:port` QUIC-адреса. Переопределяет регион                                                                    |
| `mev_protect`         | `false`            | Пропускать лидеров из блок-листа Shield. См. [Защита от MEV](/ru/apex/mev-protection)                             |
| `max_retries`         | `None`             | Бюджет повторов на транзакцию. `None` использует значение эндпоинта по умолчанию                                  |
| `bind_addr`           | Эфемерный порт     | Локальный адрес привязки UDP для списков разрешений файрвола                                                      |
| `connect_timeout`     | 3 s                | Тайм-аут рукопожатия                                                                                              |
| `send_timeout`        | 2 s                | Тайм-аут одной отправки                                                                                           |
| `keep_alive`          | 1 s                | Интервал QUIC PING. Тайм-аут простоя эндпоинта составляет 30 s                                                    |
| `auto_reconnect`      | `true`             | Когда отправка не удалась из-за потери соединения, переподключиться и отправить ещё раз                           |
| `proactive_reconnect` | `true`             | Фоновая задача выполняет новое рукопожатие, как только замечен обрыв, чтобы следующая отправка за него не платила |

```rust theme={null}
use std::time::Duration;

use apex_sender_client::{ApexSenderClient, ClientOptions, Region};

let client = ApexSenderClient::connect_with_options(
    ClientOptions {
        endpoint: Some(Region::NewYork.quic_endpoint()),
        bind_addr: Some("0.0.0.0:47001".parse()?),
        mev_protect: true,
        max_retries: Some(30),
        send_timeout: Duration::from_secs(1),
        ..Default::default()
    },
    &api_key,
)
.await?;
```

## Keep-alive и переподключения

Соединение остаётся открытым благодаря QUIC PING каждую секунду при тайм-ауте простоя 30 секунд на эндпоинте Apex. В прогретом соединении отправка сводится к одному открытию потока и одной записи.

Если соединение оборвалось:

* Фоновая задача замечает это и сразу выполняет новое рукопожатие (`proactive_reconnect`), поэтому следующая отправка обычно находит живое соединение.
* Переподключение использует закэшированный сессионный тикет и отправляет ожидающую транзакцию в первом пакете рукопожатия (**0-RTT**). Если эндпоинт отклоняет ранние данные, клиент отправляет их повторно после рукопожатия.
* Отправка, которая не удалась из-за потери соединения, переподключается и повторяется один раз (`auto_reconnect`). Установите значение `false`, чтобы обрабатывать это самостоятельно.

`health()`, `reconnects_total()` и `zero_rtt_resumptions_total()` показывают, что произошло. Экспортируйте их в свои метрики.

## Ошибки

| Вариант `Error`                                                    | Значение                                                                                                                     |
| ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| `Rejected { code, message }`                                       | Эндпоинт отклонил транзакцию в двунаправленном потоке. См. [коды допуска](/ru/apex/errors-and-rate-limits#коды-допуска-quic) |
| `TooLarge(bytes)`                                                  | Больше 4096 байт. Перехватывается локально до какой-либо отправки                                                            |
| `Timeout`                                                          | Истёк тайм-аут подключения или отправки                                                                                      |
| `Resolve`, `Bind`, `Tls`, `Connect`, `Connection`, `Write`, `Read` | Транспортные сбои                                                                                                            |
| `Serialize`, `BadAdmission`                                        | Проблемы кодирования                                                                                                         |
| `Closed`                                                           | Клиент был закрыт                                                                                                            |

HTTP-хелперы возвращают `rpc::RpcError`: `Http` для транспортных сбоев, `Rpc { code, message }` для отклонения JSON-RPC или простого HTTP-маршрута и `BadResponse`.

## Примеры в репозитории

Все примеры читают `APEX_API_KEY`, `KEYPAIR_PATH` (по умолчанию `payer.json`), `SOLANA_RPC_URL` и при необходимости `APEX_REGION`, `APEX_QUIC`, `APEX_RPC`, `TIP_LAMPORTS` и `APEX_TX_VERSION` (`legacy` или `v1`). Каждый отправляет memo с чаевыми и сообщает слот, в который оно попало.

| Пример                    | Что показывает                                                                                    |
| ------------------------- | ------------------------------------------------------------------------------------------------- |
| `quic_send`               | Однонаправленный поток: самый быстрый путь, затем подтверждение через Solana RPC                  |
| `quic_send_with_response` | Двунаправленный поток: ответ о принятии или отклонении и как читать отклонение                    |
| `rpc_send`                | JSON-RPC `sendTransaction` по HTTP с тем же правилом чаевых                                       |
| `raw_bytes`               | Заранее сериализованные байты при передаче и точная структура пакета для других языков            |
| `throughput`              | Одно прогретое соединение, N параллельных отправок, p50 и p99 на отправку, число попаданий в блок |
| `client_pubkey`           | Ключ сертификата, который выводится из API-ключа, для сравнения с вашей панелью управления        |
| `typescript/send_rpc.ts`  | Путь JSON-RPC из `@solana/web3.js` без клиентской библиотеки                                      |
| `python/send.py`          | Бинарный маршрут и JSON-RPC из Python с `solders`                                                 |

```bash theme={null}
APEX_API_KEY=... KEYPAIR_PATH=payer.json SOLANA_RPC_URL=https://... \
  cargo run --release --example quic_send --features rpc
```

Типичный вывод в mainnet от клиента, расположенного рядом с эндпоинтом Apex:

```text theme={null}
sent in 16 us
landed in slot 447831391 after 745 ms: 5gwAmfVM...
```

## Чего клиент не делает

* Не собирает и не подписывает транзакции и не выбирает вашу приоритетную комиссию.
* Не выполняет симуляцию и preflight-проверки. Этого не делает никто между вами и лидером.
