> ## 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 交易， 提供可直接运行的完整 cURL、JavaScript、Python 和 Rust 示例。

## 前提条件

您需要：

* 一个 Apex API 密钥，可在 [OrbitFlare 控制台](https://orbitflare.com/dashboard)的 **Dashboard > Apex** 下获取。Apex 目前处于测试阶段，仅限受邀用户使用。
* 一个有余额的 Solana 密钥对文件（由 `solana-keygen` 写入的 JSON 数组格式），其中至少有 0.002 SOL 用于支付小费和费用。
* 任意 Solana RPC URL，用于获取 blockhash 和确认交易，例如您的 [OrbitFlare RPC](/cn/quickstart) 端点。

每个示例都读取相同的环境变量：

```bash theme={null}
export APEX_API_KEY="YOUR_API_KEY"
export APEX_RPC="http://fra.apex.orbitflare.com"   # nearest Apex endpoint, see Endpoints and regions
export SOLANA_RPC_URL="https://api.mainnet-beta.solana.com"
export KEYPAIR_PATH="payer.json"
```

<Warning>
  这些示例会在主网上发送一笔真实交易。如果交易上链，将支付 0.001 SOL 小费以及网络费用。如果未上链，则不产生任何费用。
</Warning>

## 发送您的第一笔交易

<Steps>
  <Step title="检查 Apex 端点是否可达">
    `GET /ping` 无需密钥，返回 `pong`：

    ```bash theme={null}
    curl -s "$APEX_RPC/ping"
    ```
  </Step>

  <Step title="获取小费账户">
    ```bash theme={null}
    curl -s "$APEX_RPC" \
      -H "Content-Type: application/json" \
      -d '{"jsonrpc":"2.0","id":1,"method":"getTipAccounts","params":[]}'
    ```

    您的交易必须向这些账户之一转账，金额不低于您所在等级的下限（标准等级为 0.001 SOL）。参见[小费](/cn/apex/tips)。
  </Step>

  <Step title="构建、签名、发送并确认">
    每个标签页都是一个完整的程序。它构建一笔带有计算预算和小费的 memo 交易，通过 JSON-RPC `sendTransaction` 发送，并等待确认。

    cURL 无法对交易签名，因此 cURL 标签页使用一个小型 Node.js 脚本来构建并签名交易，然后以 base64 形式输出。之后的所有步骤都是纯 cURL。

    <CodeGroup>
      ```bash cURL theme={null}
      # 1. Save this as sign.mjs, then: npm install @solana/web3.js
      cat > sign.mjs <<'SCRIPT'
      import {
        ComputeBudgetProgram, Connection, Keypair, PublicKey, SystemProgram,
        TransactionInstruction, TransactionMessage, VersionedTransaction,
      } from "@solana/web3.js";
      import { readFileSync } from "node:fs";

      const payer = Keypair.fromSecretKey(
        Uint8Array.from(JSON.parse(readFileSync(process.env.KEYPAIR_PATH ?? "payer.json", "utf8"))),
      );
      const connection = new Connection(process.env.SOLANA_RPC_URL, "confirmed");
      const { blockhash } = await connection.getLatestBlockhash("confirmed");
      const message = new TransactionMessage({
        payerKey: payer.publicKey,
        recentBlockhash: blockhash,
        instructions: [
          ComputeBudgetProgram.setComputeUnitLimit({ units: 100_000 }),
          ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 10_000 }),
          new TransactionInstruction({
            programId: new PublicKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"),
            keys: [{ pubkey: payer.publicKey, isSigner: true, isWritable: true }],
            data: Buffer.from(`apex curl ${Date.now()}`),
          }),
          SystemProgram.transfer({
            fromPubkey: payer.publicKey,
            toPubkey: new PublicKey(process.argv[2]),
            lamports: 1_000_000,
          }),
        ],
      }).compileToV0Message();
      const tx = new VersionedTransaction(message);
      tx.sign([payer]);
      console.log(Buffer.from(tx.serialize()).toString("base64"));
      SCRIPT

      # 2. Pick a tip account and sign a transaction that tips it.
      TIP_ACCOUNT=$(curl -s "$APEX_RPC" -H "Content-Type: application/json" \
        -d '{"jsonrpc":"2.0","id":1,"method":"getTipAccounts","params":[]}' | jq -r '.result[0]')
      TX_BASE64=$(node sign.mjs "$TIP_ACCOUNT")

      # 3. Send it. params: [base64 transaction, config, mevProtect]
      SIGNATURE=$(curl -s "$APEX_RPC" \
        -H "Content-Type: application/json" \
        -H "x-api-key: $APEX_API_KEY" \
        -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"sendTransaction\",\"params\":[\"$TX_BASE64\",{\"encoding\":\"base64\"},false]}" \
        | jq -r '.result // .error')
      echo "accepted: $SIGNATURE"

      # 4. Confirm on any Solana RPC. Repeat until confirmationStatus is "confirmed".
      curl -s "$SOLANA_RPC_URL" -H "Content-Type: application/json" \
        -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"getSignatureStatuses\",\"params\":[[\"$SIGNATURE\"]]}" | jq '.result.value[0]'
      ```

      ```javascript JavaScript theme={null}
      // send.mjs
      //   npm install @solana/web3.js
      //   node send.mjs
      import {
        ComputeBudgetProgram, Connection, Keypair, PublicKey, SystemProgram,
        TransactionInstruction, TransactionMessage, VersionedTransaction,
      } from "@solana/web3.js";
      import { readFileSync } from "node:fs";

      const APEX_RPC = process.env.APEX_RPC ?? "http://fra.apex.orbitflare.com";
      const API_KEY = process.env.APEX_API_KEY;
      const SOLANA_RPC = process.env.SOLANA_RPC_URL ?? "https://api.mainnet-beta.solana.com";
      const TIP_LAMPORTS = Number(process.env.TIP_LAMPORTS ?? 1_000_000);
      const MEMO_PROGRAM = new PublicKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr");

      async function apexRpc(method, params) {
        const res = await fetch(APEX_RPC, {
          method: "POST",
          headers: { "Content-Type": "application/json", "x-api-key": API_KEY },
          body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
        });
        const body = await res.json();
        if (body.error) throw new Error(`${method} failed ${body.error.code}: ${body.error.message}`);
        return body.result;
      }

      if (!API_KEY) throw new Error("APEX_API_KEY is required");
      const payer = Keypair.fromSecretKey(
        Uint8Array.from(JSON.parse(readFileSync(process.env.KEYPAIR_PATH ?? "payer.json", "utf8"))),
      );
      const connection = new Connection(SOLANA_RPC, "confirmed");

      const tipAccounts = await apexRpc("getTipAccounts", []);
      const tipAccount = new PublicKey(tipAccounts[Math.floor(Math.random() * tipAccounts.length)]);

      const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash("confirmed");
      const message = new TransactionMessage({
        payerKey: payer.publicKey,
        recentBlockhash: blockhash,
        instructions: [
          ComputeBudgetProgram.setComputeUnitLimit({ units: 100_000 }),
          ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 10_000 }),
          new TransactionInstruction({
            programId: MEMO_PROGRAM,
            keys: [{ pubkey: payer.publicKey, isSigner: true, isWritable: true }],
            data: Buffer.from(`apex javascript ${Date.now()}`),
          }),
          SystemProgram.transfer({ fromPubkey: payer.publicKey, toPubkey: tipAccount, lamports: TIP_LAMPORTS }),
        ],
      }).compileToV0Message();
      const tx = new VersionedTransaction(message);
      tx.sign([payer]);

      const started = Date.now();
      // params: [base64 transaction, { encoding, maxRetries? }, mevProtect]
      const signature = await apexRpc("sendTransaction", [
        Buffer.from(tx.serialize()).toString("base64"),
        { encoding: "base64" },
        false,
      ]);
      console.log(`accepted in ${Date.now() - started} ms: ${signature}`);

      const result = await connection.confirmTransaction({ signature, blockhash, lastValidBlockHeight }, "confirmed");
      if (result.value.err) throw new Error(`failed on chain: ${JSON.stringify(result.value.err)}`);
      console.log(`confirmed after ${Date.now() - started} ms`);
      ```

      ```python Python theme={null}
      # send.py
      #   pip install solders requests
      #   python send.py
      import base64
      import json
      import os
      import random
      import time

      import requests
      from solders.compute_budget import set_compute_unit_limit, set_compute_unit_price
      from solders.hash import Hash
      from solders.instruction import AccountMeta, Instruction
      from solders.keypair import Keypair
      from solders.message import Message
      from solders.pubkey import Pubkey
      from solders.system_program import TransferParams, transfer
      from solders.transaction import Transaction

      APEX_RPC = os.environ.get("APEX_RPC", "http://fra.apex.orbitflare.com")
      API_KEY = os.environ["APEX_API_KEY"]
      SOLANA_RPC = os.environ.get("SOLANA_RPC_URL", "https://api.mainnet-beta.solana.com")
      TIP_LAMPORTS = int(os.environ.get("TIP_LAMPORTS", "1000000"))
      MEMO_PROGRAM = Pubkey.from_string("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr")


      def apex_rpc(method, params):
          r = requests.post(APEX_RPC, headers={"x-api-key": API_KEY},
                            json={"jsonrpc": "2.0", "id": 1, "method": method, "params": params}, timeout=5)
          body = r.json()
          if "error" in body:
              raise RuntimeError(f"{method} failed {body['error']['code']}: {body['error']['message']}")
          return body["result"]


      def solana_rpc(method, params):
          r = requests.post(SOLANA_RPC, json={"jsonrpc": "2.0", "id": 1, "method": method, "params": params}, timeout=10)
          return r.json()["result"]


      def confirm(signature, timeout_s=30.0):
          deadline = time.time() + timeout_s
          while time.time() < deadline:
              status = solana_rpc("getSignatureStatuses", [[signature]])["value"][0]
              if status and status.get("err"):
                  raise RuntimeError(f"failed on chain: {status['err']}")
              if status and status.get("confirmationStatus") in ("confirmed", "finalized"):
                  return status["slot"]
              time.sleep(0.5)
          return None


      with open(os.environ.get("KEYPAIR_PATH", "payer.json")) as f:
          payer = Keypair.from_bytes(bytes(json.load(f)))

      tip_account = Pubkey.from_string(random.choice(apex_rpc("getTipAccounts", [])))
      blockhash = Hash.from_string(
          solana_rpc("getLatestBlockhash", [{"commitment": "confirmed"}])["value"]["blockhash"])

      instructions = [
          set_compute_unit_limit(100_000),
          set_compute_unit_price(10_000),
          Instruction(MEMO_PROGRAM, f"apex python {time.time_ns()}".encode(),
                      [AccountMeta(payer.pubkey(), is_signer=True, is_writable=True)]),
          transfer(TransferParams(from_pubkey=payer.pubkey(), to_pubkey=tip_account, lamports=TIP_LAMPORTS)),
      ]
      tx = Transaction([payer], Message.new_with_blockhash(instructions, payer.pubkey(), blockhash), blockhash)

      started = time.perf_counter()
      # params: [base64 transaction, {encoding, maxRetries?}, mevProtect]
      signature = apex_rpc("sendTransaction", [base64.b64encode(bytes(tx)).decode(), {"encoding": "base64"}, False])
      print(f"accepted in {(time.perf_counter() - started) * 1000:.1f} ms: {signature}")
      print("landed in slot", confirm(signature))
      ```

      ```rust Rust theme={null}
      // src/main.rs. Cargo.toml dependencies are listed under "Rust dependencies" below.
      //   cargo run --release
      use std::str::FromStr;
      use std::time::{Duration, Instant};

      use apex_sender_client::rpc::{RpcClient, SolanaRpc};
      use apex_sender_client::{tip, tip_instruction, ApexSenderClient, Region, MIN_TIP_LAMPORTS};
      use solana_compute_budget_interface::ComputeBudgetInstruction;
      use solana_instruction::{AccountMeta, Instruction};
      use solana_keypair::read_keypair_file;
      use solana_pubkey::Pubkey;
      use solana_signer::Signer;
      use solana_transaction::versioned::VersionedTransaction;
      use solana_transaction::Transaction;

      #[tokio::main]
      async fn main() -> Result<(), Box<dyn std::error::Error>> {
          let api_key = std::env::var("APEX_API_KEY")?;
          let solana_rpc_url = std::env::var("SOLANA_RPC_URL")
              .unwrap_or_else(|_| "https://api.mainnet-beta.solana.com".into());
          let keypair_path = std::env::var("KEYPAIR_PATH").unwrap_or_else(|_| "payer.json".into());
          let payer = read_keypair_file(keypair_path)?;
          let region = Region::Frankfurt;

          // One persistent QUIC connection, authenticated by a certificate derived from the key.
          let client = ApexSenderClient::connect(region, &api_key).await?;
          let tip_accounts = RpcClient::new(region, &api_key).get_tip_accounts().await?;
          let tip_account = tip::pick_tip_account(&tip_accounts).ok_or("no tip accounts")?;

          let nonce = std::time::SystemTime::now()
              .duration_since(std::time::UNIX_EPOCH)?
              .as_nanos();
          let memo = Instruction::new_with_bytes(
              Pubkey::from_str("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr")?,
              format!("apex rust {nonce}").as_bytes(),
              vec![AccountMeta::new(payer.pubkey(), true)],
          );
          let instructions = [
              ComputeBudgetInstruction::set_compute_unit_limit(100_000),
              ComputeBudgetInstruction::set_compute_unit_price(10_000),
              memo,
              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 sent_at = Instant::now();
          let signature = client.send_transaction(&tx).await?; // unidirectional stream, no acknowledgement
          println!("sent in {} us", sent_at.elapsed().as_micros());

          match solana.confirm(&signature.to_string(), Duration::from_secs(30)).await? {
              Some(slot) => println!("landed in slot {slot} after {} ms: {signature}", sent_at.elapsed().as_millis()),
              None => println!("not confirmed within 30 s: {signature}"),
          }
          Ok(())
      }
      ```
    </CodeGroup>
  </Step>
</Steps>

成功的 `sendTransaction` 会返回交易的签名：

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "5gwAmfVM..."
}
```

<Note>
  **已接受并不代表已上链。** 一旦 Apex 端点持有交易并开始竞速发送，签名就会立即返回。请务必像每个示例那样，在 Solana RPC 上使用 `getSignatureStatuses` 进行确认。
</Note>

## Rust 依赖

Rust 标签页使用 QUIC 客户端，这是最快的路径。该 crate 即将以 `apex-sender-client` 的名称发布到 crates.io。在此之前，请从 OrbitFlare 的 GitHub 仓库安装：

```toml theme={null}
[dependencies]
apex-sender-client = { git = "https://github.com/orbitflare/apex-sender-client", features = ["rpc"] }
tokio = { version = "1", features = ["full"] }
solana-compute-budget-interface = "3"
solana-instruction = "3.4"
solana-keypair = "3.1"
solana-pubkey = "4.2"
solana-signer = "3"
solana-transaction = { version = "=4.1.6", features = ["wincode"] }
```

完整 API 参见 [Rust 客户端](/cn/apex/rust-client)。

## 已经在调用 sendTransaction？

Apex 使用标准的 Solana `sendTransaction` 格式，因此迁移现有代码只需替换 URL，再加上两处改动：

1. 将您的**发送**调用指向 `http://<code>.apex.orbitflare.com`，并添加 `x-api-key` 请求头（或 `?api-key=`）。
2. 为每笔交易添加[小费指令](/cn/apex/tips)。

其他所有操作请继续使用您常规的 Solana RPC：获取 blockhash、读取账户、模拟和确认。Apex 端点只接受发送类请求。

## 下一步

<CardGroup cols={2}>
  <Card title="发送交易" icon="paper-plane" href="/cn/apex/sending-transactions">
    二进制 HTTP、批量和 QUIC 流。
  </Card>

  <Card title="小费" icon="coins" href="/cn/apex/tips">
    完整的小费规则以及小费的用途。
  </Card>

  <Card title="错误与速率限制" icon="triangle-exclamation" href="/cn/apex/errors-and-rate-limits">
    每一种拒绝代码及其处理方法。
  </Card>

  <Card title="最佳实践" icon="list-check" href="/cn/apex/best-practices">
    连接预热、小费设置、确认和重试。
  </Card>
</CardGroup>
