> ## 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

> Доставляйте в блок больше транзакций Solana с OrbitFlare Apex: держите соединения прогретыми, подбирайте чаевые и вычислительный бюджет, подтверждайте каждую отправку и повторяйте правильно.

## Держите соединения прогретыми

Самая медленная часть холодной отправки заключается в установке соединения, а не в Apex. Открывайте соединение заранее и держите его открытым.

<Tabs>
  <Tab title="QUIC">
    [Клиент на Rust](/ru/apex/rust-client) делает это за вас. Он отправляет пинг каждую секунду при 30-секундном тайм-ауте простоя эндпоинта Apex, возобновляет сессию с 0-RTT и выполняет новое рукопожатие в фоне, как только замечен обрыв.

    * Создавайте **один клиент на процесс и эндпоинт Apex** при запуске, а не по одному на отправку. Потоки мультиплексируются в одном соединении.
    * `Clone` клиента обходится дёшево, и клоны разделяют соединение. Передавайте клон каждой задаче.
    * Экспортируйте `health()`, `reconnects_total()` и `zero_rtt_resumptions_total()` в свои метрики, чтобы видеть обрывы.

    Если вы реализуете QUIC сами, отправляйте QUIC PING с большим запасом до 30-секундного тайм-аута простоя и храните сессионный тикет для 0-RTT.
  </Tab>

  <Tab title="HTTP">
    * **Переиспользуйте одно соединение.** Используйте клиент, который поддерживает соединения открытыми: `requests.Session` в Python, общий `reqwest::Client` в Rust или keep-alive агент в Node.js. Новое TCP-соединение на каждую отправку добавляет полный обмен с сервером.
    * **Вызывайте `GET /ping` во время простоя.** Он не требует ключа, возвращает `pong` и не даёт соединению закрыться между всплесками. Достаточно одного вызова раз в несколько секунд.
    * **Прогревайтесь при запуске.** Вызовите `/ping` один раз перед первой настоящей отправкой, чтобы рукопожатие уже было выполнено.

    ```python theme={null}
    import threading

    import requests

    session = requests.Session()  # reuse this session for every send

    def keep_warm(stop: threading.Event, url="http://fra.apex.orbitflare.com/ping"):
        while not stop.wait(5):
            session.get(url, timeout=2)

    stop = threading.Event()
    threading.Thread(target=keep_warm, args=(stop,), daemon=True).start()
    ```
  </Tab>
</Tabs>

## Отправляйте поближе к эндпоинту Apex

Выберите [эндпоинт Apex](/ru/apex/endpoints) с наименьшим временем обмена от вашего отправителя и измеряйте его с помощью `/ping`, а не угадывайте по карте. Каждый эндпоинт Apex направляет транзакции клиентам валидаторов, ближайшим к следующим лидерам, поэтому ваша задача состоит только в том, чтобы быстро достичь эндпоинта.

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

## Соизмеряйте чаевые со сделкой

Минимум (0.001 SOL на стандартном тарифе) задаёт порог для принятия транзакции, а не рекомендацию для каждой транзакции.

* Чаевые оплачивают вашу **ставку Jito**: чаевые за вычетом базовой комиссии 5,000 lamports. В блоке с высокой конкуренцией ставка на уровне минимума может проиграть аукцион. Пути с весом по стейку и TPU всё равно работают, но вы теряете один из трёх своих путей.
* Вы платите чаевые, **только когда транзакция попала в блок**, поэтому более крупные чаевые на важной транзакции ничего не стоят при промахе.
* Масштабируйте чаевые вместе с ценностью попадания в блок. Рутинный перевод может оставаться на минимуме. Для конкурентной сделки чаевые должны быть пропорциональны тому, сколько для вас стоит попасть в блок первым.

Если вы не уверены в своём тарифе, прочитайте минимум в сообщении об отклонении: ошибка о чаевых ниже минимума его указывает.

## Задавайте вычислительный бюджет в каждой транзакции

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

* **Лимит вычислительных единиц:** просимулируйте транзакцию на своём обычном RPC, возьмите число потреблённых единиц и добавьте запас примерно от 10 до 20 процентов. Лимит по умолчанию, когда вы его не задаёте, намного выше, чем нужно большинству транзакций, и это впустую расходует ваш приоритет.
* **Цена вычислительной единицы:** следите за рынком для аккаунтов, в которые вы пишете. `getRecentPrioritizationFees` с вашими записываемыми аккаунтами даёт разумный стартовый сигнал.
* Ставьте инструкции ComputeBudget **первыми** в транзакции.
* Это относится к транзакциям legacy и v0. [Транзакция v1](/ru/apex/transaction-v1) задаёт бюджет в `TransactionConfig` сообщения, и инструкции ComputeBudget там не действуют.

Memo-транзакции в этой документации используют лимит 100,000 единиц, потому что программа memo необычно дорогая. Это плохой ориентир для вашего собственного лимита. Подбирайте его под свои инструкции.

## Подтверждайте каждую отправку

Подпись в ответе означает **принята**, а не попала в блок.

* Опрашивайте `getSignatureStatuses` на Solana RPC или подпишитесь через `signatureSubscribe`, пока статус не станет `confirmed` или `finalized`.
* Проверяйте поле `err`. Транзакция может попасть в блок и всё равно завершиться ошибкой в блокчейне. Чаевые являются частью транзакции, поэтому транзакция, которая завершилась ошибкой при исполнении, откатывает и перевод чаевых, и вы платите только сетевую комиссию.
* Прекращайте ждать, когда истекает blockhash. Отслеживайте `lastValidBlockHeight` из `getLatestBlockhash`. Как только блокчейн его прошёл, транзакция уже никогда не попадёт в блок.

В Rust `rpc::SolanaRpc::confirm(signature, timeout)` выполняет опрос и возвращает слот, `None` при тайм-ауте или ошибку, если транзакция завершилась ошибкой в блокчейне.

## Дайте Apex повторять, затем соберите заново

Apex повторно отправляет принятую транзакцию, пока она не попадёт в блок или пока не истечёт её blockhash. Опирайтесь на это, а не обходите:

* **Не отправляйте принятую транзакцию повторно в цикле.** Это ничего не даёт и расходует ваш лимит запросов.
* **Используйте свежий blockhash.** Получайте его с уровнем подтверждения `confirmed` непосредственно перед подписанием. Blockhash, которому уже минута, оставляет Apex очень мало времени для работы.
* **Когда blockhash истёк, соберите транзакцию заново.** Получите новый blockhash, подпишите заново и отправьте новую транзакцию. Перед этим убедитесь, что первая уже не может попасть в блок, либо используйте логику, которая безопасна при исполнении обеих.
* **Повторяйте при транспортных сбоях.** Если отправка не удалась до получения ответа, отправьте те же байты ещё раз. Эндпоинт отсеивает дубликаты по подписи.
* **Делайте паузу при `rate limited` и `busy`.** См. [какие ошибки повторять](/ru/apex/errors-and-rate-limits#какие-ошибки-повторять).

`maxRetries` ограничивает число повторных отправок транзакции со стороны Apex. Оставьте значение по умолчанию, если только вы не хотите, чтобы транзакция сдавалась раньше, например котировка, которая устаревает через секунду-две.

## Интегрируйтесь с обратной связью, работайте без неё

На время интеграции используйте транспорт, который сообщает, почему транзакция была отклонена: JSON-RPC, HTTP-маршруты или двунаправленный QUIC-поток. Когда ваши транзакции начнут стабильно приниматься, переведите горячие пути на однонаправленные QUIC-потоки, а двунаправленную или HTTP-отправку оставьте для отладки.

## Делайте каждую транзакцию уникальной

Apex отсеивает дубликаты по подписи. Две транзакции с одинаковыми инструкциями, подписантами и blockhash имеют одну и ту же подпись и считаются одной. Если вы намерены отправить одно и то же действие дважды, измените что-нибудь, например memo или цену вычислительной единицы на один микро-lamport.

## Чек-лист

<AccordionGroup>
  <Accordion title="Перед запуском в продакшен">
    * Одни чаевые на транзакцию, верхний уровень, оплачены подписантом, аккаунт для чаевых в статических ключах
    * Аккаунт для чаевых выбирается случайно для каждой транзакции из закэшированного списка `getTipAccounts`
    * Лимит и цена вычислительных единиц в каждой транзакции
    * Blockhash получен непосредственно перед подписанием
    * Соединение открыто и прогрето при запуске
    * Каждая отправка подтверждена, поле `err` проверено
    * Пауза при rate limited и busy, сборка заново при invalid
    * API-ключ в переменной окружения или хранилище секретов, а не в коде
  </Accordion>
</AccordionGroup>
