> ## 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 最佳实践

> 使用 OrbitFlare Apex 让更多 Solana 交易上链：保持连接预热、合理设置小费和计算预算、 确认每一次发送，并以正确的方式重试。

## 保持连接预热

冷启动发送中最慢的部分是建立连接，而不是 Apex。请在需要之前就打开连接，并保持其打开。

<Tabs>
  <Tab title="QUIC">
    [Rust 客户端](/cn/apex/rust-client)会为您处理这一切。针对 Apex 端点 30 秒的空闲超时，它每秒 ping 一次，使用 0-RTT 恢复连接，并在发现断线后立即在后台重新握手。

    * 在启动时为**每个进程和每个 Apex 端点创建一个客户端**，而不是每次发送创建一个。各个流在这一个连接上多路复用。
    * 客户端的 `Clone` 开销很低，克隆出的副本共享同一个连接。给每个任务分发一个克隆。
    * 将 `health()`、`reconnects_total()` 和 `zero_rtt_resumptions_total()` 导出到您的指标中，以便观察断线情况。

    如果您自行实现 QUIC，请在远小于 30 秒空闲超时的间隔内发送 QUIC PING，并保留会话票据以用于 0-RTT。
  </Tab>

  <Tab title="HTTP">
    * **复用同一个连接。** 使用能够保持连接存活的客户端：Python 中的 `requests.Session`、Rust 中共享的 `reqwest::Client`，或 Node.js 中的 keep-alive agent。每次发送都新建 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 端点](/cn/apex/endpoints)，并用 `/ping` 实际测量，而不是看地图猜测。每个 Apex 端点都会路由到离即将出块的领导者最近的验证者客户端，因此您只需要尽快到达端点。

如果您在多个区域运行，请从每个区域发送到各自最近的端点。将**同一笔已签名交易**发送到多个 Apex 端点是安全的，因为网络对一个签名只执行一次，但每一份副本都会计入您的速率限制。

## 根据交易价值确定小费

下限（标准等级为 0.001 SOL）是被接受的最低要求，并不是对每笔交易的建议值。

* 小费为您的 **Jito 出价**提供资金：出价等于小费减去 5,000 lamports 基础费用。在竞争激烈的区块中，仅达到下限的出价可能会在拍卖中落败。质押加权路径和 TPU 路径仍会运行，但您放弃了三条路径中的一条。
* 您**只在交易上链时**支付小费，因此为重要交易设置更高的小费，在未上链时不会产生任何费用。
* 让小费与上链的价值成比例。常规转账可以保持在下限。竞争性交易的小费应与抢先上链对您的价值成正比。

如果不确定自己的等级，可以从拒绝消息中读取下限：低于下限的错误会注明该值。

## 为每笔交易设置计算预算

Apex 把您送达领导者。随后领导者的调度器按优先费排序。如果没有计算预算，您就只能在队列末尾竞争。

* **计算单元上限**：在您的常规 RPC 上模拟交易，取其消耗的单元数，再加上约 10% 到 20% 的余量。未设置时的默认上限远高于大多数交易的需要，这会浪费您的优先级。
* **计算单元价格**：跟随您所写入账户的市场行情。以您的可写账户调用 `getRecentPrioritizationFees` 是一个合理的起始参考。
* 将 ComputeBudget 指令放在交易的**最前面**。
* 以上适用于 legacy 和 v0 交易。[交易 v1](/cn/apex/transaction-v1) 改为在消息的 `TransactionConfig` 中设置预算，ComputeBudget 指令在那里不起作用。

本文档中的 memo 交易使用 100,000 单元的上限，因为 memo 程序的开销异常高。它不适合作为您自己上限的参考。请根据您自己的指令来确定大小。

## 确认每一次发送

响应中的签名表示**已接受**，而不是已上链。

* 在 Solana RPC 上轮询 `getSignatureStatuses`，或使用 `signatureSubscribe` 订阅，直到状态为 `confirmed` 或 `finalized`。
* 检查 `err` 字段。交易可能上链但仍在链上执行失败。小费是交易的一部分，因此执行期间失败的交易也会回滚小费转账，您只需支付网络费用。
* blockhash 过期后停止等待。跟踪 `getLatestBlockhash` 返回的 `lastValidBlockHeight`。一旦链超过该高度，交易就永远无法上链。

在 Rust 中，`rpc::SolanaRpc::confirm(signature, timeout)` 会执行轮询并返回 slot，超时时返回 `None`，交易在链上失败时返回错误。

## 让 Apex 重试，然后重新构建

Apex 会重新发送已接受的交易，直到它上链或其 blockhash 过期。请配合这一机制，而不是绕开它：

* **不要循环重发已接受的交易。** 这没有任何帮助，还会消耗您的速率限制。
* **使用新的 blockhash。** 在签名前一刻以 `confirmed` 承诺级别获取。一个已经存在一分钟的 blockhash 留给 Apex 的时间非常少。
* **blockhash 过期后，重新构建。** 获取新的 blockhash，重新签名，并发送新交易。在此之前请确保第一笔交易已无法上链，或者使用即使两笔都执行也安全的逻辑。
* **重试传输失败。** 如果发送在收到响应之前失败，请再次发送相同的字节。端点按签名去重。
* **遇到 `rate limited` 和 `busy` 时退避。** 参见[哪些错误可以重试](/cn/apex/errors-and-rate-limits#哪些错误可以重试)。

`maxRetries` 限制 Apex 重新发送一笔交易的次数。除非您希望交易提前放弃，例如一两秒后就过时的报价，否则请保持默认值。

## 集成时使用反馈，运行时无需反馈

在集成阶段，请使用能告诉您交易为何被拒绝的传输方式：JSON-RPC、HTTP 路由或双向 QUIC 流。一旦您的交易能够稳定地被接受，就将热路径迁移到单向 QUIC 流，并保留一个双向流或 HTTP 发送方式用于调试。

## 让每笔交易都唯一

Apex 按签名去重。两笔指令、签名者和 blockhash 完全相同的交易具有相同的签名，会被视为一笔。如果您打算将同一操作发送两次，请改动某些内容，例如 memo，或将计算单元价格调整一个 micro-lamport。

## 检查清单

<AccordionGroup>
  <Accordion title="上线之前">
    * 每笔交易一笔小费，位于顶层，由签名者出资，小费账户位于静态密钥中
    * 每笔交易从缓存的 `getTipAccounts` 列表中随机选择小费账户
    * 每笔交易都设置计算单元上限和价格
    * 在签名前一刻获取 blockhash
    * 在启动时打开连接并预热
    * 确认每一次发送，并检查 `err` 字段
    * 遇到限速和繁忙时退避，遇到无效时重新构建
    * API 密钥存放在环境变量或密钥存储中，而不是代码里
  </Accordion>
</AccordionGroup>
