> ## 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 TypeScript 客户端

> 使用 @orbitflare/sdk 中的 ApexClient，从 TypeScript 通过 OrbitFlare Apex 发送交易、批量交易和捆绑包。仅支持 HTTP JSON-RPC，提供区域故障转移和小费辅助工具。

> [!NOTE]
>
> TypeScript Apex 客户端是 [`@orbitflare/sdk`](/cn/sdk/overview) 的一部分，而不是单独的包。它**仅通过 HTTP JSON-RPC** 与 Apex 通信。TypeScript 目前还没有 QUIC 传输，因为 Node 没有足够可靠、可用于发送的 QUIC 实现。如需通过 QUIC 获得最低延迟的路径，请使用 [Rust 客户端](/cn/apex/rust-client)或 [Go 客户端](/cn/apex/go-client)。

## 安装

```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
npm install @orbitflare/sdk @solana/kit @solana-program/system @solana-program/compute-budget
```

`@solana/kit` 和这两个程序包用于构建和签名交易。Apex 客户端本身只需要 `@orbitflare/sdk`。

设置您的 Apex API 密钥、一个有余额的密钥对，以及用于获取 blockhash 和确认交易的 Solana RPC 端点。示例使用 OrbitFlare RPC 和您的许可证密钥：

```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
export ORBITFLARE_APEX_API_KEY=apex_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export ORBITFLARE_LICENSE_KEY=ORBIT-XXXXXX-NNNNNN-NNNNNN
export KEYPAIR_PATH=payer.json
export SOLANA_RPC_URL="https://fra.rpc.orbitflare.com?api_key=$ORBITFLARE_LICENSE_KEY"
```

## 快速开始

该程序以最低小费向您选择的地址（默认是它自己）发送 1,000 lamports，然后等待交易上链。将其保存为 `send.ts`，并使用 `npx tsx send.ts` 运行。

```ts theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
import { readFileSync } from 'node:fs';

import {
  address,
  appendTransactionMessageInstructions,
  createKeyPairSignerFromBytes,
  createTransactionMessage,
  getSignatureFromTransaction,
  getTransactionEncoder,
  lamports,
  pipe,
  setTransactionMessageFeePayerSigner,
  setTransactionMessageLifetimeUsingBlockhash,
  signTransactionMessageWithSigners,
  type Blockhash,
} from '@solana/kit';
import {
  getSetComputeUnitLimitInstruction,
  getSetComputeUnitPriceInstruction,
} from '@solana-program/compute-budget';
import { getTransferSolInstruction } from '@solana-program/system';

import {
  APEX_DEFAULT_COMPUTE_UNIT_PRICE_MICRO_LAMPORTS,
  APEX_MIN_TIP_LAMPORTS,
  ApexClientBuilder,
  RpcClientBuilder,
  pickApexTipAccount,
  waitForConfirmation,
} from '@orbitflare/sdk';

async function main() {
  // Reads ORBITFLARE_APEX_API_KEY; sends over HTTP to Frankfurt.
  const apex = new ApexClientBuilder().region('fra').build();
  const solana = new RpcClientBuilder()
    .url(process.env.SOLANA_RPC_URL ?? 'https://api.mainnet-beta.solana.com')
    .build();

  const keypair = JSON.parse(readFileSync(process.env.KEYPAIR_PATH ?? 'payer.json', 'utf8'));
  const payer = await createKeyPairSignerFromBytes(Uint8Array.from(keypair));
  const to = address(process.env.TO ?? payer.address);

  // A tip to any other account is rejected, so pick from the published list.
  const tipAccount = pickApexTipAccount(await apex.getTipAccounts());
  const { blockhash, lastValidBlockHeight } = await solana.getLatestBlockhash();

  const message = pipe(
    createTransactionMessage({ version: 'legacy' }),
    (m) => setTransactionMessageFeePayerSigner(payer, m),
    (m) =>
      setTransactionMessageLifetimeUsingBlockhash(
        { blockhash: blockhash as Blockhash, lastValidBlockHeight: BigInt(lastValidBlockHeight) },
        m,
      ),
    (m) =>
      appendTransactionMessageInstructions(
        [
          getSetComputeUnitLimitInstruction({ units: 50_000 }),
          getSetComputeUnitPriceInstruction({
            microLamports: APEX_DEFAULT_COMPUTE_UNIT_PRICE_MICRO_LAMPORTS,
          }),
          getTransferSolInstruction({ source: payer, destination: to, amount: lamports(1_000n) }),
          // Exactly one top-level tip of at least 0.001 SOL.
          getTransferSolInstruction({
            source: payer,
            destination: address(tipAccount),
            amount: lamports(BigInt(APEX_MIN_TIP_LAMPORTS)),
          }),
        ],
        m,
      ),
  );
  const tx = await signTransactionMessageWithSigners(message);
  const wire = getTransactionEncoder().encode(tx);

  const sentAt = Date.now();
  const signature = await apex.sendTransactionBinary(wire);
  console.log(`accepted in ${Date.now() - sentAt} ms: ${signature}`);
  console.log(`matches the signed transaction: ${signature === getSignatureFromTransaction(tx)}`);

  // Accepted is not landed: confirm on a Solana RPC.
  const slot = await waitForConfirmation(solana, signature);
  console.log(
    slot === null
      ? 'not confirmed within 30 s'
      : `landed in slot ${slot} after ${Date.now() - sentAt} ms`,
  );
}

void main();
```

下面的代码片段复用该程序中的 `apex`、`solana` 以及已签名的交易字节（`wire`）。

## 发送

有两个方法用于发送单笔交易。两者都会在端点接受交易后立即返回签名。

| 方法 | 路由 | 请求体 |
| - | - | - |
| `sendTransactionBinary(bytes)` | `POST /send-bin` | 原始交易字节。最快的 HTTP 路径，无需任何编码 |
| `sendTransaction(bytesOrBase64)` | JSON-RPC `sendTransaction` | JSON-RPC 请求中的 base64。接受字节或 base64 字符串 |

```ts theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
const signature = await apex.sendTransaction(wire, { mevProtect: true, maxRetries: 10 });
```

两者都接受可选的 `mevProtect`（跳过 Shield 黑名单上的领导者，参见 [MEV 保护](/cn/apex/mev-protection)）和 `maxRetries`（限制 Apex 重新提交的次数），它们会在该次调用中覆盖构建器的默认值。交易大小必须在 1 到 4,096 字节之间（`APEX_MAX_TRANSACTION_SIZE`）。

## 批量发送

`sendBatch` 在一个请求中发送最多 16 笔交易（`APEX_MAX_BATCH`）。每笔交易都会被单独检查和竞速发送，因此一笔有问题的交易不会影响其他交易。

```ts theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
const batch = await apex.sendBatch([wire1, wire2, wire3]);
console.log(`${batch.accepted} of ${batch.attempted} accepted`);
for (const item of batch.results) {
  console.log(item.accepted ? item.signature : `${item.error}: ${item.message}`);
}
```

## 捆绑包

`sendBundle` 发送 1 到 4 笔交易（`APEX_MAX_BUNDLE`），它们要么一起上链，要么都不上链，并且只通过 Jito 领导者发送。捆绑包中恰好有一笔交易携带小费。

```ts theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
const { bundleId, signatures } = await apex.sendBundle([wire1, wire2]);
const [status] = await apex.getBundleStatuses([bundleId]);
console.log(status?.state, status?.landedSlot);
```

`state` 为 `Pending`、`Landed`、`Failed` 或 `Invalid`。捆绑包状态可能落后于链上状态，因此请使用 `waitForConfirmation` 确认捆绑包的 `signatures`，而不要只等待状态。规则和速率限制参见[捆绑包](/cn/apex/bundles)。

## 小费与确认

| 辅助工具 | 作用 |
| - | - |
| `apex.getTipAccounts()` | 端点接受的小费账户 |
| `pickApexTipAccount(accounts)` | 从中随机选择一个，以分散写锁 |
| `APEX_MIN_TIP_LAMPORTS` | 标准等级的小费下限：1,000,000 lamports（0.001 SOL） |
| `APEX_DEFAULT_COMPUTE_UNIT_PRICE_MICRO_LAMPORTS` | 合理的默认优先费：每计算单元 10,000 micro-lamports |
| `fetchApexVaults(rpc)` | 从链上读取小费程序的金库账户。这不是小费列表：它包含端点会拒绝的未发布金库。请使用 `getTipAccounts()` 获取可供打小费的账户，而此方法仅用于检查某个账户是否为真实的金库 |
| `waitForConfirmation(rpc, signature, { timeoutMs, pollMs })` | 轮询直到交易被确认。返回 slot，超时（默认 30 s）时返回 `null`，如果交易已上链但执行失败则抛出异常 |

小费规则参见[小费](/cn/apex/tips)页面。

## 区域与故障转移

```ts theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
const apex = new ApexClientBuilder()
  .region('fra')
  .fallbackRegion('ams')
  .fallbackRegion('lon')
  .build();
```

当对主端点的请求失败时，客户端会按顺序尝试备用端点。区域代码为 `fra`、`ams`、`dub`、`lon`、`nyc`、`slc`、`sgp`、`tyo`、`sqq` 和 `global`（最近的健康端点）。未设置区域时，客户端依次使用 `ORBITFLARE_APEX_URL`、`ORBITFLARE_APEX_REGION`，最后是法兰克福。参见[端点与区域](/cn/apex/endpoints)。

## 选项

| 构建器方法 | 默认值 | 作用 |
| - | - | - |
| `.apiKey(key)` | `ORBITFLARE_APEX_API_KEY` | 您的 Apex API 密钥，作为 `x-api-key` 请求头发送 |
| `.region(code)` / `.url(url)` | 法兰克福 | 主端点 |
| `.fallbackRegion(code)` / `.fallbackUrl(url)` | 无 | 主端点失败时尝试的端点 |
| `.mevProtect(enabled)` | `false` | 每次发送都跳过 Shield 黑名单上的领导者。参见 [MEV 保护](/cn/apex/mev-protection) |
| `.maxRetries(n)` | Apex 默认值 | Apex 重新提交每笔交易的次数 |
| `.retry(policy)` | 每个端点 1 次尝试 | 在同一端点上对失败请求进行带退避的客户端重试 |
| `.timeoutMs(ms)` | `5000` | 单次请求超时 |

默认情况下，客户端对每个端点只尝试一次，然后直接转向下一个备用端点，而不是重试。这是有意为之：重新发送 Apex 已接受的交易会浪费您的速率限制，而且 Apex 自己会持续重新提交已接受的交易。

## 保持连接预热

`apex.ping()` 调用 `GET /ping`，并在端点应答 `pong` 时完成。每隔几秒调用一次可保持 HTTP 连接打开，这样下一次发送就能跳过 TCP 握手。

```ts theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
setInterval(() => apex.ping().catch(() => {}), 5_000);
```

## 错误

每个失败都是一个带有 `kind` 的 `OrbitflareError`：

| `kind` | 含义 |
| - | - |
| `config` | 缺少 API 密钥、未知区域，或交易、批量或捆绑包超出大小限制。未发送任何内容 |
| `rpc` | Apex 对 JSON-RPC 调用返回了错误，例如小费不足或交易无效。错误中包含代码和消息 |
| `apex` | 某个二进制路由（`/send-bin`、`/send-batch`、`/send-bundle`）拒绝了请求。错误中包含 HTTP 状态、标签和消息 |
| `rateLimited` | 超过了您的密钥的每秒限制（HTTP 429）。当 Apex 提供时，`retryAfterMs` 表示需要等待多久 |
| `timeout` | 在 `timeoutMs` 内没有应答 |
| `transport` | 请求在尝试过的每个端点上都失败了，或端点返回了服务器错误 |

Apex 错误码和速率限制列于[错误与速率限制](/cn/apex/errors-and-rate-limits)。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.