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

> Send your first tipped Solana transaction through OrbitFlare Apex in minutes, with complete cURL, JavaScript, Python, and Rust examples you can run as is.

## Prerequisites

You need:

* An Apex API key from the [OrbitFlare dashboard](https://orbitflare.com/dashboard) under **Dashboard > Apex**. Apex is in beta and invite only.
* A funded Solana keypair file (the JSON array format written by `solana-keygen`), with at least 0.002 SOL for the tip and fees.
* Any Solana RPC URL for fetching a blockhash and confirming, for example your [OrbitFlare RPC](/quickstart) endpoint.

Every example reads the same environment variables:

```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>
  These examples send a real transaction on mainnet. If it lands, it pays a 0.001 SOL tip plus the network fee. If it does not land, it costs nothing.
</Warning>

## Send Your First Transaction

<Steps>
  <Step title="Check that the Apex endpoint is reachable">
    `GET /ping` needs no key and returns `pong`:

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

  <Step title="Fetch the tip accounts">
    ```bash theme={null}
    curl -s "$APEX_RPC" \
      -H "Content-Type: application/json" \
      -d '{"jsonrpc":"2.0","id":1,"method":"getTipAccounts","params":[]}'
    ```

    Your transaction must transfer at least your tier's floor (0.001 SOL on the standard tier) to one of these accounts. See [Tips](/apex/tips).
  </Step>

  <Step title="Build, sign, send, and confirm">
    Each tab is a complete program. It builds a memo transaction with a compute budget and a tip, sends it with JSON-RPC `sendTransaction`, and waits for confirmation.

    cURL cannot sign a transaction, so the cURL tab uses a small Node.js script to build and sign one and print it as base64. Everything after that is plain 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>

A successful `sendTransaction` returns the transaction's signature:

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

<Note>
  **Accepted does not mean landed.** The signature comes back as soon as the Apex endpoint holds the transaction and starts racing it. Always confirm with `getSignatureStatuses` on a Solana RPC, as each example does.
</Note>

## Rust Dependencies

The Rust tab uses the QUIC client, which is the fastest path. The crate is coming to crates.io as `apex-sender-client`. Until then, install it from the OrbitFlare GitHub repository:

```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"] }
```

See [Rust client](/apex/rust-client) for the full API.

## Already Calling sendTransaction?

Apex speaks the standard Solana `sendTransaction` shape, so moving existing code over is a URL swap plus two changes:

1. Point your **send** calls at `http://<code>.apex.orbitflare.com` and add the `x-api-key` header (or `?api-key=`).
2. Add the [tip instruction](/apex/tips) to every transaction.

Keep using your regular Solana RPC for everything else: blockhashes, account reads, simulation, and confirmation. Apex endpoints only accept sends.

## What's Next?

<CardGroup cols={2}>
  <Card title="Sending transactions" icon="paper-plane" href="/apex/sending-transactions">
    Binary HTTP, batches, and QUIC streams.
  </Card>

  <Card title="Tips" icon="coins" href="/apex/tips">
    The tip rule in full and how the tip is used.
  </Card>

  <Card title="Errors and rate limits" icon="triangle-exclamation" href="/apex/errors-and-rate-limits">
    Every rejection code and what to do about it.
  </Card>

  <Card title="Best practices" icon="list-check" href="/apex/best-practices">
    Warm connections, tip sizing, confirming, and retries.
  </Card>
</CardGroup>
